运动播报修改
authorliangzhenying <867174124@qq.com>
Thu, 26 Feb 2026 07:16:57 +0000 (15:16 +0800)
committerliangzhenying <867174124@qq.com>
Thu, 26 Feb 2026 07:16:57 +0000 (15:16 +0800)
82 files changed:
ShenYin/DeepChordsSdk/DCTts.swift [new file with mode: 0644]
ShenYin/Podfile.lock
ShenYin/ShenYin.xcodeproj/project.pbxproj
ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Sdk/LogManager.swift
ShenYin/ShenYin/UI/Base/BaseTC.swift
ShenYin/ShenYin/UI/Sport/Music/MusicRootView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Record/RecordHeadItem.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Record/RecordRootView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Create/AddGroupHeader.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Create/CreateGroupVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportInputItem.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSelectItem.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSwitchItem.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Detail/GroupDetailVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberCell.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberModel.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupCell.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupModel.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupRoleVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupTypeVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Group/add/AddGroupVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/History/SportHistoryVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/History/SprotHistoryView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Light/BrightnessView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Light/LightSetVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Light/ModeView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Light/StateView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Ready/CountdownView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Ready/CustomMapView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Ready/DeviceStateView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Ready/FooterView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Ready/Navbar.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyHeader.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Root/DataView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Root/GroupView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Root/PlanView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Root/RouteView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Root/SportRootView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Root/SportTypeView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetCell.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetModel.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/CadenceView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/ElevationView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/FeelView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PacePerView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PaceView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailBoard.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StepRateView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StrideLengthView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Sporting/DetailBoard.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Sporting/SimpleBoard.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Sporting/SportingVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Target/CalorieView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Target/DistanceView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Target/DurationView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Target/SportTargetVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Tools/IBSpeaker.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Tools/MapManager.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Tools/SportCalculateUtil.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Sport/Tools/SportManager.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/SportHomeVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Sport/Translate/TranslateRootView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/common/NotificationManager.swift
ShenYin/podfile

diff --git a/ShenYin/DeepChordsSdk/DCTts.swift b/ShenYin/DeepChordsSdk/DCTts.swift
new file mode 100644 (file)
index 0000000..acf2de2
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  DCTts.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/26.
+//
+import StarburstSdk
+public class DCTts:NSObject{
+    public static let shared = DCTts()
+    
+    ///
+    var ttsCallBack:((_ stream:Data)->Void)?
+    
+    /// 文本合成语音
+    /// - Parameters:
+    ///   - text: 文本
+    ///   - callback: 音频流回调
+    public  func tts(text:String,callback: @escaping ((Data))->Void){
+        self.ttsCallBack = callback
+        TextToSpeechManager.sharedInstance().textToSpeech(
+            companyId: "2101982539",
+            text: text,
+            reqId: UUID().uuidString,
+            speedRatio: 1.0,
+            voiceType: "zh_female_qingchezizi_moon_bigtts",
+            encoding: "pcm",
+            callback: self
+        )
+    }
+}
+extension DCTts: TextToSpeechCallback {
+    public func onResult(_ response: String) {
+        guard let jsonObj = try? JSONSerialization.jsonObject(with: response.data(using: .utf8)!, options: []) as? [String:Any],
+              let respObj = jsonObj["resp"] as? [String:Any],
+              let base64Data = respObj["data"] as? String,
+              let pcmBytes = Data(base64Encoded: base64Data) else {
+            DCLogger.debug("解析PCM失败\n")
+            return
+        }
+       ttsCallBack?(pcmBytes)
+    }
+    
+    public func onError(_ code: Int, message: String) {
+        DCLogger.debug("合成失败:\(message)\n")
+    }
+}
index 7d0c168..9df3bbc 100644 (file)
@@ -1,4 +1,11 @@
 PODS:
+  - AMap3DMap-NO-IDFA (10.0.700):
+    - AMapFoundation-NO-IDFA (>= 1.8.0)
+  - AMapFoundation-NO-IDFA (1.8.2)
+  - AMapLocation-NO-IDFA (2.10.0):
+    - AMapFoundation-NO-IDFA (>= 1.8.0)
+  - AMapSearch-NO-IDFA (9.7.0):
+    - AMapFoundation-NO-IDFA (>= 1.8.0)
   - CryptoSwift (1.8.4)
   - DeviceKit (5.4.0)
   - DGActivityIndicatorView (2.1.1)
@@ -70,6 +77,9 @@ PODS:
   - ZipArchive (1.4.0)
 
 DEPENDENCIES:
+  - AMap3DMap-NO-IDFA (= 10.0.700)
+  - AMapLocation-NO-IDFA
+  - AMapSearch-NO-IDFA (= 9.7.0)
   - CryptoSwift
   - DeviceKit (~> 5.4.0)
   - DGActivityIndicatorView
@@ -81,7 +91,6 @@ DEPENDENCIES:
   - JXPagingView/Paging
   - JXSegmentedView
   - Kingfisher
-  - Kingfisher (~> 6.0)
   - MBProgressHUD
   - MJRefresh
   - SnapKit
@@ -98,6 +107,10 @@ DEPENDENCIES:
 
 SPEC REPOS:
   https://gitee.com/mirrors/CocoaPods-Specs.git:
+    - AMap3DMap-NO-IDFA
+    - AMapFoundation-NO-IDFA
+    - AMapLocation-NO-IDFA
+    - AMapSearch-NO-IDFA
     - CryptoSwift
     - DeviceKit
     - DGActivityIndicatorView
@@ -131,6 +144,10 @@ SPEC REPOS:
     - ZipArchive
 
 SPEC CHECKSUMS:
+  AMap3DMap-NO-IDFA: e029d65ea35d3e4c0a948e1dab9e77109bf0e86a
+  AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05
+  AMapLocation-NO-IDFA: 735791aeb1ba52dff666e4158b86caf4b68b5be7
+  AMapSearch-NO-IDFA: 749f73d5a773758f01a24eb8872f8798c8b47cb4
   CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90
   DeviceKit: bd34bb1766d926c3cc5a5f6e3e9a6dd10148b805
   DGActivityIndicatorView: ff2c76073d79692f724863c8dd38685866a03cb6
@@ -163,6 +180,6 @@ SPEC CHECKSUMS:
   YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
   ZipArchive: e25a4373192673e3229ac8d6e9f64a3e5713c966
 
-PODFILE CHECKSUM: d3adf5685042140a1efe3de6a225914ce9255e5c
+PODFILE CHECKSUM: 6c1824ce6ff4ee834e74a20980186ec7c76f1d1c
 
 COCOAPODS: 1.16.2
index 4db690f..75fb146 100644 (file)
                                3F887B9A2E38D8DC00C98658 /* Resources */,
                                5A934A35F54A5620CFD3C08A /* [CP] Embed Pods Frameworks */,
                                3FEFE9092F1B252000D82F17 /* Embed Frameworks */,
+                               169D8C02ED63ABC1BD3F8125 /* [CP] Copy Pods Resources */,
                        );
                        buildRules = (
                        );
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
+               169D8C02ED63ABC1BD3F8125 /* [CP] Copy Pods Resources */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputFileListPaths = (
+                               "${PODS_ROOT}/Target Support Files/Pods-ShenYin/Pods-ShenYin-resources-${CONFIGURATION}-input-files.xcfilelist",
+                       );
+                       inputPaths = (
+                       );
+                       name = "[CP] Copy Pods Resources";
+                       outputFileListPaths = (
+                               "${PODS_ROOT}/Target Support Files/Pods-ShenYin/Pods-ShenYin-resources-${CONFIGURATION}-output-files.xcfilelist",
+                       );
+                       outputPaths = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ShenYin/Pods-ShenYin-resources.sh\"\n";
+                       showEnvVarsInLog = 0;
+               };
                3FD1DFE62F0E00BF00A62733 /* [CP] Check Pods Manifest.lock */ = {
                        isa = PBXShellScriptBuildPhase;
                        buildActionMask = 2147483647;
                                CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements;
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 100041;
+                               CURRENT_PROJECT_VERSION = 202602241648;
                                DEVELOPMENT_TEAM = MAC46RQ4KH;
                                FRAMEWORK_SEARCH_PATHS = (
                                        "$(inherited)",
                                CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements;
                                CODE_SIGN_IDENTITY = "Apple Development";
                                CODE_SIGN_STYLE = Automatic;
-                               CURRENT_PROJECT_VERSION = 100041;
+                               CURRENT_PROJECT_VERSION = 202602241648;
                                DEVELOPMENT_TEAM = MAC46RQ4KH;
                                FRAMEWORK_SEARCH_PATHS = (
                                        "$(inherited)",
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Contents.json
new file mode 100644 (file)
index 0000000..2942dce
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame_2255@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame_2255@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@2x.png
new file mode 100644 (file)
index 0000000..fe1f134
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@3x.png
new file mode 100644 (file)
index 0000000..842ec4f
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_call.imageset/Frame_2255@3x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Contents.json
new file mode 100644 (file)
index 0000000..2942dce
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame_2255@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame_2255@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@2x.png
new file mode 100644 (file)
index 0000000..45eb136
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@3x.png
new file mode 100644 (file)
index 0000000..c0c4c74
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_collection.imageset/Frame_2255@3x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Contents.json
new file mode 100644 (file)
index 0000000..2942dce
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame_2255@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame_2255@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@2x.png
new file mode 100644 (file)
index 0000000..1da43ce
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@3x.png
new file mode 100644 (file)
index 0000000..3baf0a4
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_music_list.imageset/Frame_2255@3x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Contents.json
new file mode 100644 (file)
index 0000000..2942dce
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame_2255@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame_2255@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@2x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@2x.png
new file mode 100644 (file)
index 0000000..629ea8e
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@3x.png b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@3x.png
new file mode 100644 (file)
index 0000000..9baf1e0
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/sport/sport_record.imageset/Frame_2255@3x.png differ
index f61e054..8735333 100644 (file)
@@ -9,7 +9,7 @@ import Foundation
 
 /// 日记管理
 class Logger {
-    static var isDebug: Bool = false
+    static var isDebug: Bool = true
     
     fileprivate static let shared = Logger()
     private var fileHandle:FileHandle?
index 907eba5..f1a8f9f 100644 (file)
@@ -20,8 +20,8 @@ class BaseTC:UITabBarController{
 
     func setupUI(){
         if CCDevice.isJoy{
-            let homeVC = HomeDeviceVC()
-//            let homeVC = SportHomeVC()
+//            let homeVC = HomeDeviceVC()
+            let homeVC = SportHomeVC()
 
             let modeVC = ModeVC()
             let sleepVC = SleepVC()
diff --git a/ShenYin/ShenYin/UI/Sport/Music/MusicRootView.swift b/ShenYin/ShenYin/UI/Sport/Music/MusicRootView.swift
new file mode 100644 (file)
index 0000000..a74cc6c
--- /dev/null
@@ -0,0 +1,53 @@
+//
+//  MusicVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/20.
+//
+
+
+import UIKit
+import JXSegmentedView
+
+class MusicRootView: BaseView{
+    let content = SprotRocrodView()
+    
+    override func setupViews() {
+        addSubviews([content])
+        content.do { v in
+            v.header.titleLabel.text = "音乐".l10n
+            v.header.detailLabel.isHidden = true
+            v.header.icon.isHidden = true
+            v.item1.titleLabel.text = "我的歌单".l10n
+            v.item1.icon.image = UIImage(named: "sport_music_list")
+            v.item2.titleLabel.text = "我的收藏".l10n
+            v.item2.icon.image = UIImage(named: "sport_music_collection")
+//            v.header.yc.addTap(target: self, action: #selector(historyAction))
+            v.item1.yc.addTap(target: self, action: #selector(recordAction))
+            v.item2.yc.addTap(target: self, action: #selector(callAction))
+        }
+    }
+    
+    override func setupLayout() {
+        content.snp.makeConstraints { make in
+            make.left.right.top.equalToSuperview().inset(18)
+            make.height.equalTo(209)
+        }
+    }
+//    @objc func historyAction(){
+//        App.shared.goToPage(HistoryRecordVC())
+//    }
+    @objc func recordAction(){
+        App.shared.goToPage(HistoryRecordVC())
+    }
+    @objc func callAction(){
+        App.shared.goToPage(HistoryRecordVC())
+    }
+}
+
+extension MusicRootView: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return self
+    }
+}
+
diff --git a/ShenYin/ShenYin/UI/Sport/Record/RecordHeadItem.swift b/ShenYin/ShenYin/UI/Sport/Record/RecordHeadItem.swift
new file mode 100644 (file)
index 0000000..51f4273
--- /dev/null
@@ -0,0 +1,117 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/25.
+//
+class SprotRocrodView: BaseView {
+    let header = RecordheadItem()
+    let line = UIView()
+    let item1 = SportRecordItem()
+    let item2 = SportRecordItem()
+    
+    override func setupViews() {
+        yc.setCornerRadius(12)
+        backgroundColor = .color(.cellBg)
+        addSubviews([header,line,item1,item2])
+        line.backgroundColor = .color(.separateLine)
+    }
+    
+    override func setupLayout() {
+        header.snp.makeConstraints { make in
+            make.left.right.top.equalToSuperview()
+            make.height.equalTo(51)
+        }
+        item1.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(15)
+            make.height.equalTo(60)
+            make.top.equalToSuperview().offset(61)
+        }
+        item2.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(15)
+            make.height.equalTo(60)
+            make.top.equalTo(item1.snp.bottom).offset(14)
+        }
+        line.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(14)
+            make.height.equalTo(1)
+            make.top.equalToSuperview().inset(47)
+        }
+    }
+}
+class SportRecordItem: BaseView {
+    let icon = UIImageView()
+    let titleLabel = UILabel()
+    
+    override func setupViews() {
+        yc.setCornerRadius(12)
+        backgroundColor = .color(.bgColor)
+        addSubviews([icon,titleLabel])
+        
+        titleLabel.do { v in
+            v.textColor = .color(.first)
+            v.font = .medium(14)
+            v.text = "".l10n
+        }
+        icon.do { v in
+            v.image = UIImage(named: "arrow_2")
+        }
+    }
+    
+    override func setupLayout() {
+        
+        titleLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(60)
+        }
+        icon.snp.makeConstraints { make in
+            make.size.equalTo(32)
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(14)
+        }
+    }
+}
+
+class RecordheadItem: BaseView {
+    let icon = UIImageView()
+    let detailLabel = UILabel()
+    let titleLabel = UILabel()
+    
+    override func setupViews() {
+        yc.setCornerRadius(12)
+        addSubviews([icon,titleLabel,detailLabel])
+        
+        titleLabel.do { v in
+            v.textColor = .color(.first)
+            v.font = .medium(14)
+            v.text = "".l10n
+        }
+        detailLabel.do { v in
+            v.textColor = .color(.secondary)
+            v.font = .regular(12)
+            v.text = "历史记录".l10n
+        }
+        icon.do { v in
+            v.image = UIImage(named: "arrow_2")
+        }
+        backgroundColor = .white
+    }
+    
+    override func setupLayout() {
+        
+        titleLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(14)
+        }
+        detailLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.right.equalToSuperview().inset(27)
+        }
+        icon.snp.makeConstraints { make in
+            make.width.equalTo(7)
+            make.height.equalTo(13)
+            make.centerY.equalToSuperview()
+            make.right.equalToSuperview().inset(12)
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Record/RecordRootView.swift b/ShenYin/ShenYin/UI/Sport/Record/RecordRootView.swift
new file mode 100644 (file)
index 0000000..e1cbc9a
--- /dev/null
@@ -0,0 +1,50 @@
+//
+//  RecordView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/20.
+//
+
+import UIKit
+import JXSegmentedView
+
+class RecordRootView: BaseView{
+    let content = SprotRocrodView()
+    
+    override func setupViews() {
+        addSubviews([content])
+        content.do { v in
+            v.header.titleLabel.text = "录音".l10n
+            v.item1.titleLabel.text = "现场录音".l10n
+            v.item1.icon.image = UIImage(named: "sport_record")
+            v.item2.titleLabel.text = "通话录音".l10n
+            v.item2.icon.image = UIImage(named: "sport_call")
+            v.header.yc.addTap(target: self, action: #selector(historyAction))
+            v.item1.yc.addTap(target: self, action: #selector(recordAction))
+            v.item2.yc.addTap(target: self, action: #selector(callAction))
+        }
+    }
+    
+    override func setupLayout() {
+        content.snp.makeConstraints { make in
+            make.left.right.top.equalToSuperview().inset(18)
+            make.height.equalTo(209)
+        }
+    }
+    @objc func historyAction(){
+        App.shared.goToPage(HistoryRecordVC())
+    }
+    @objc func recordAction(){
+        CCPage.gotoFieldRecord()
+    }
+    @objc func callAction(){
+        CCPage.gotoCallRecord()
+    }
+}
+
+extension RecordRootView: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return self
+    }
+}
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/AddGroupHeader.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/AddGroupHeader.swift
new file mode 100644 (file)
index 0000000..f9912ab
--- /dev/null
@@ -0,0 +1,40 @@
+//
+//  AddGroupHeader.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+class AddGroupHeader: BaseView {
+    let icon = UIImageView()
+    let titleLabel = UILabel()
+    
+    override func setupViews() {
+        addSubviews([icon,titleLabel])
+        
+        titleLabel.do { v in
+            v.textColor = .color(.secondary)
+            v.font = .regular(10)
+            v.text = "设置团队头像".l10n
+        }
+
+        icon.do { v in
+            v.image = UIImage(named: "group_avatar")
+            yc.setCornerRadius(12)
+
+        }
+    }
+    
+    override func setupLayout() {
+        icon.snp.makeConstraints { make in
+            make.size.equalTo(90)
+            make.centerX.equalToSuperview()
+            make.top.equalToSuperview()
+        }
+        titleLabel.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.top.equalTo(icon.snp.bottom).offset(6)
+        }
+
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/CreateGroupVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/CreateGroupVC.swift
new file mode 100644 (file)
index 0000000..c77bec8
--- /dev/null
@@ -0,0 +1,144 @@
+//
+//  CreateGroupVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/5.
+//
+
+import UIKit
+
+class CreateGroupVC: BaseVC{
+    let headerView = AddGroupHeader()
+    let nameView = SportInputItem()
+    let typeView = SportSelectItem()
+    let desView = SportInputItem()
+    let lightView = SportSelectItem()
+    let verifyView = SportSwitchItem()
+    let okBtn = CustomButton()
+    var sportType:SportType = .walk
+    
+    override func setupViews() {
+        navBar.do { v in
+            v.title = "创建团队".l10n
+            v.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+            v.rightView.isHidden = false
+        }
+        view.addSubviews([headerView,nameView,typeView,desView,lightView,verifyView,okBtn])
+        nameView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "团队名称".l10n
+            v.textField.text = "111"
+            v.textField.attributedPlaceholder = NSAttributedString(string: "给团队起个名字".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)])
+
+        }
+        typeView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "团队类型".l10n
+            v.detailLabel.text = sportType.name
+            v.yc.addTap(target: self, action: #selector(typeAction))
+        }
+        desView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "简介".l10n
+            v.textField.attributedPlaceholder = NSAttributedString(string: "描述一下您的团".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)])
+        }
+        lightView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "灯带设置".l10n
+            v.yc.addTap(target: self, action: #selector(lightAction))
+
+        }
+        verifyView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "加入验证".l10n
+            v.detailLabel.text = "开启后,成员需要申请才能加入".l10n
+        }
+        okBtn.set(title: "完成创建".l10n,style: .theme1, target: self, action: #selector(okAction))
+    }
+    override func setupLayout() {
+        headerView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview()
+            make.top.equalToSuperview().inset(kNavBtm + 20)
+            make.height.equalTo(120)
+        }
+        nameView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(headerView.snp.bottom).offset(10)
+            make.height.equalTo(51)
+        }
+        typeView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(nameView.snp.bottom).offset(10)
+            make.height.equalTo(51)
+        }
+        desView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(typeView.snp.bottom).offset(10)
+            make.height.equalTo(65)
+        }
+        lightView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(desView.snp.bottom).offset(10)
+            make.height.equalTo(51)
+        }
+        verifyView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(lightView.snp.bottom).offset(10)
+            make.height.equalTo(65)
+        }
+        okBtn.snp.makeConstraints { make in
+            make.bottom.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview().inset(51)
+            make.height.equalTo(46)
+        }
+    }
+    override func navRightViewTapped() {
+        let tip = GroupDesPopView()
+        tip.show()
+    }
+    @objc func typeAction(){
+        let vc = GroupTypeVC()
+        vc.didSelected = {[weak self] value in
+            self?.sportType = value
+            self?.typeView.detailLabel.text = value.name
+        }
+        App.shared.goToPage(vc)
+    }
+    @objc func lightAction(){
+        App.shared.goToPage(LightSetVC())
+    }
+    @objc func okAction(){
+        if nameView.isEmpty  {
+            YLprogressHUD.showAutoHiden("团队名称不能为空".l10n)
+            return
+        }
+        
+        YLprogressHUD.showLoading()
+        var params:[String:Any] = [:]
+        // 团队名称,必须
+        params["name"] = nameView.value
+        // 头像
+//        params["icon"] = ""
+        // 运动类型:running | cycling | walking,必须
+        params["type"] = sportType.rawValue
+        // 团队简介,非必须
+//        params["introduction"] = ""
+        // 1加入审核,0不需要审核,非必须,默认0
+        params["joinReview"] = verifyView.iSwitch.isOn ? "1":"0"
+        let api =  CommomApi.createSportGroup(param: params)
+        HttpClient.request(api){ (model:CommonDataModel, res) in
+            
+            if model.isSuccess{
+                YLprogressHUD.showSuccess {
+                    App.shared.popPage(animated: false)
+                    let vc = GroupDetailVC(groupId:model.data )
+                    App.shared.goToPage(vc)
+                }
+            }
+            else{
+                YLprogressHUD.showAutoHiden("请求失败".l10n)
+            }
+        }
+    }
+}
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportInputItem.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportInputItem.swift
new file mode 100644 (file)
index 0000000..4c10566
--- /dev/null
@@ -0,0 +1,136 @@
+//
+//  SportInputItem.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+class SportInputItem: BaseView,UITextFieldDelegate {
+    let valueLabel = UILabel()
+    let textField = UITextField()
+    let titleLabel = UILabel()
+    var maxLength = 10
+    
+    var isEmpty:Bool{
+        if let text = textField.text,text.isNotEmpty {
+            return false
+        }
+        return true
+    }
+    var value:String{
+        return textField.text ?? ""
+    }
+    
+    override func setupViews() {
+        yc.setCornerRadius(12)
+        addSubviews([valueLabel,titleLabel,textField])
+        
+        titleLabel.do { v in
+            v.textColor = .color(.first)
+            v.font = .medium(14)
+        }
+        textField.do { v in
+            v.textColor = .color(.first)
+            v.keyboardType = .default
+            v.returnKeyType = .done
+            v.textAlignment = .left
+            v.addTarget(self, action: #selector(textFieldValueChange), for: .valueChanged)
+//            v.backgroundColor = .red
+            v.delegate = self
+        }
+        valueLabel.do { v in
+            v.textColor = .color(.placeholder)
+            v.font = .regular(10)
+            v.textAlignment = .right
+            v.text = "0/\(maxLength)"
+        }
+        backgroundColor = .white
+    }
+    
+    override func setupLayout() {
+        
+        titleLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(14)
+        }
+        textField.snp.makeConstraints { make in
+            make.left.equalTo(titleLabel.snp.right).offset(10).priority(.high)
+            make.centerY.height.equalToSuperview()
+            make.right.equalToSuperview().inset(50).priority(.low)
+        }
+        valueLabel.snp.makeConstraints { make in
+            make.width.equalTo(38)
+            make.height.equalToSuperview()
+            make.centerY.equalToSuperview()
+            make.right.equalToSuperview().inset(12)
+        }
+    }
+    @objc func textFieldValueChange(){
+        valueLabel.text = "\(textField.text?.count ?? 0)/\(maxLength)"
+    }
+    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+        textField.resignFirstResponder()
+        return false
+    }
+}
+
+class SportTextViewItem: BaseView,UITextViewDelegate {
+    let valueLabel = UILabel()
+    let textView = UITextView()
+    let titleLabel = UILabel()
+    var maxLength = 10
+    
+    override func setupViews() {
+        yc.setCornerRadius(12)
+        addSubviews([valueLabel,titleLabel,textView])
+        
+        titleLabel.do { v in
+            v.textColor = .color(.first)
+            v.font = .medium(14)
+            v.text = "简介".l10n
+        }
+        textView.do { v in
+            v.textColor = .color(.first)
+            v.keyboardType = .default
+            v.returnKeyType = .done
+            v.textAlignment = .left
+            v.delegate = self
+        }
+        valueLabel.do { v in
+            v.textColor = .color(.placeholder)
+            v.font = .regular(10)
+            v.textAlignment = .right
+            v.text = "0/\(maxLength)"
+        }
+        backgroundColor = .white
+    }
+    
+    override func setupLayout() {
+        
+        titleLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(14)
+        }
+        textView.snp.makeConstraints { make in
+            make.left.equalTo(titleLabel.snp.right).offset(10).priority(.high)
+            make.centerY.height.equalToSuperview()
+            make.right.equalToSuperview().inset(50).priority(.low)
+        }
+        valueLabel.snp.makeConstraints { make in
+            make.width.equalTo(38)
+            make.height.equalToSuperview()
+            make.centerY.equalToSuperview()
+            make.right.equalToSuperview().inset(12)
+        }
+    }
+    
+    func textViewDidChange(_ textView: UITextView) {
+        valueLabel.text = "\(textView.text?.count ?? 0)/\(maxLength)"
+    }
+
+    func textView(_ textView: UITextView, shouldChangeTextInRanges ranges: [NSValue], replacementText text: String) -> Bool {
+        textView.resignFirstResponder()
+        
+        return false
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSelectItem.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSelectItem.swift
new file mode 100644 (file)
index 0000000..51d8041
--- /dev/null
@@ -0,0 +1,48 @@
+//
+//  SportItem.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+class SportSelectItem: BaseView {
+    let icon = UIImageView()
+    let detailLabel = UILabel()
+    let titleLabel = UILabel()
+    
+    override func setupViews() {
+        yc.setCornerRadius(12)
+        addSubviews([icon,titleLabel,detailLabel])
+        
+        titleLabel.do { v in
+            v.textColor = .color(.first)
+            v.font = .medium(14)
+        }
+        detailLabel.do { v in
+            v.textColor = .color(.secondary)
+            v.font = .regular(12)
+        }
+        icon.do { v in
+            v.image = UIImage(named: "arrow_2")
+        }
+        backgroundColor = .white
+    }
+    
+    override func setupLayout() {
+        
+        titleLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(14)
+        }
+        detailLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.right.equalToSuperview().inset(27)
+        }
+        icon.snp.makeConstraints { make in
+            make.width.equalTo(7)
+            make.height.equalTo(13)
+            make.centerY.equalToSuperview()
+            make.right.equalToSuperview().inset(12)
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSwitchItem.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Create/SportSwitchItem.swift
new file mode 100644 (file)
index 0000000..4f5423a
--- /dev/null
@@ -0,0 +1,51 @@
+//
+//  SportSwitchItem.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+class SportSwitchItem: BaseTableViewCell {
+    let titleLabel = UILabel()
+    let detailLabel = UILabel()
+    let iSwitch = UISwitch()
+  
+
+    override func setupViews() {
+        addSubviews([titleLabel,detailLabel,iSwitch])
+        
+        backgroundColor = .color(.cellBg)
+        titleLabel.do { (v) in
+            v.textColor = .color(.first)
+            v.font = .bold(14)
+            v.textAlignment = .left
+        }
+        detailLabel.do { (v) in
+            v.textColor = .color(.secondary)
+            v.font = .regular(10)
+            v.textAlignment = .left
+        }
+        iSwitch.do { v in
+            v.addTarget(self, action: #selector(swicthAction), for: .valueChanged)
+        }
+    }
+    
+    override func setupLayout() {
+        titleLabel.snp.makeConstraints { (m) in
+            m.top.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(16)
+        }
+        detailLabel.snp.makeConstraints { (m) in
+            m.bottom.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(16)
+        }
+        iSwitch.snp.makeConstraints { (m) in
+            m.right.equalToSuperview().inset(15)
+            m.centerY.equalToSuperview()
+        }
+    }
+
+  
+    @objc func swicthAction(){
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Detail/GroupDetailVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Detail/GroupDetailVC.swift
new file mode 100644 (file)
index 0000000..bbfc751
--- /dev/null
@@ -0,0 +1,29 @@
+//
+//  GroupDetailVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/25.
+//
+
+import UIKit
+
+class GroupDetailVC: BaseVC{
+    var groupId = ""
+    
+    
+    init(groupId:String) {
+        self.groupId = groupId
+        super.init()
+    }
+    
+    override func setupViews() {
+        navBar.do { v in
+            v.title = "团队详情".l10n
+        }
+      
+    }
+    override func setupLayout() {
+      
+    }
+   
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberCell.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberCell.swift
new file mode 100644 (file)
index 0000000..048f84e
--- /dev/null
@@ -0,0 +1,161 @@
+//
+//  GroupMemberCell.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/12.
+//
+
+class GroupMemberCell: BaseTableViewCell {
+    let bgView = UIView()
+    let icon = UIImageView()
+    let nameLabel = UILabel()
+    let roleType = UIImageView()
+    let roleLabel = UILabel()
+    let moreIcon = UIImageView()
+    let line = UIView()
+    var model:GroupMemberModel!{
+        didSet{
+            icon.kf.setImage(with: model.avatar.yc.asUrl)
+            nameLabel.text =  model.userName
+            roleLabel.text = model.role.name
+            roleType.backgroundColor = model.role.color
+//            moreIcon.isHidden = model.userId != CacheManager.shared.user.info.userId
+        }
+    }
+    
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    override func setupViews() {
+        contentView.addSubview(bgView)
+        bgView.addSubviews([icon,nameLabel,roleLabel,roleType,moreIcon,line])
+        
+        backgroundColor = .color(.bgColor)
+        contentView.backgroundColor = .clear
+        selectionStyle = .none
+        bgView.do { v in
+            v.backgroundColor = .color(.cellBg)
+//            v.yc.setCornerRadius(18)
+        }
+        nameLabel.do { (v) in
+            v.textColor = .color(.first)
+            v.font = .medium(12)
+            v.textAlignment = .left
+            v.lineBreakMode = .byTruncatingTail
+        }
+        roleLabel.do { v in
+            v.textColor = .color(.secondary)
+            v.font = .regular(10)
+            v.textAlignment = .left
+            v.lineBreakMode = .byTruncatingTail
+        }
+        roleType.do { v in
+            v.yc.setCornerRadius(3)
+        }
+        icon.do { v in
+            v.yc.setCornerRadius(18)
+        }
+        moreIcon.do { v in
+            v.image = UIImage(named: "more")
+            v.contentMode = .center
+            v.yc.addTap(target: self, action: #selector(moreAction))
+        }
+        line.do { v in
+            v.backgroundColor = .color(.separateLine)
+        }
+    }
+    
+    override func setupLayout() {
+        bgView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview().inset(18)
+            m.top.equalToSuperview()
+            m.bottom.equalToSuperview()
+        }
+        
+        icon.snp.makeConstraints { (m) in
+            m.size.equalTo(36)
+            m.left.equalToSuperview().inset(15)
+            m.top.equalToSuperview().inset(15)
+        }
+        nameLabel.snp.makeConstraints { (m) in
+            m.top.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(60)
+            m.right.lessThanOrEqualToSuperview().inset(16)
+        }
+        roleType.snp.makeConstraints { make in
+            make.left.equalTo(nameLabel)
+            make.bottom.equalToSuperview().inset(16)
+            make.width.equalTo(6)
+            make.height.equalTo(10)
+        }
+        roleLabel.snp.makeConstraints { (m) in
+            m.bottom.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(77)
+            m.right.lessThanOrEqualToSuperview().inset(16)
+        }
+        moreIcon.snp.makeConstraints { make in
+            make.right.equalToSuperview()
+            make.centerY.equalToSuperview()
+            make.width.equalTo(18+18+5)
+            make.height.equalToSuperview()
+        }
+        line.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.bottom.equalToSuperview()
+            make.height.equalTo(0.4)
+        }
+    }
+    
+    @objc func moreAction(){
+        let vc = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+        let action0 = UIAlertAction(title: "团长转让".l10n, style: .default) {[weak self] action in
+            self?.transferGroup()
+        }
+        let action1 = UIAlertAction(title: "移除团队".l10n, style: .default) {[weak self] action in
+            self?.removeFromGroup()
+        }
+        let action2 = UIAlertAction(title: "取消".l10n, style: .cancel) { action in
+            
+        }
+        vc.addAction(action0)
+        vc.addAction(action1)
+        vc.addAction(action2)
+        App.shared.topVC().present(vc, animated: true)
+    }
+    @objc func removeFromGroup(){
+        let vc = UIAlertController(title: "提示".l10n, message: "确认要把TA移出团队吗?".l10n, preferredStyle: .alert)
+        let action0 = UIAlertAction(title: "确定".l10n, style: .destructive) {[weak self] action in
+            self?.removeFromGroup2()
+        }
+        let action1 = UIAlertAction(title: "取消".l10n, style: .cancel) {[weak self] action in
+        }
+        
+        vc.addAction(action0)
+        vc.addAction(action1)
+        App.shared.topVC().present(vc, animated: true)
+    }
+    @objc func removeFromGroup2(){
+        
+    }
+    
+    @objc func transferGroup(){
+        let vc = UIAlertController(title: "提示".l10n, message: "确认要把团长转让给TA吗?".l10n, preferredStyle: .alert)
+        let action0 = UIAlertAction(title: "确定".l10n, style: .destructive) {[weak self] action in
+            self?.transferGroup2()
+        }
+        let action1 = UIAlertAction(title: "取消".l10n, style: .cancel) {[weak self] action in
+        }
+
+        vc.addAction(action0)
+        vc.addAction(action1)
+        App.shared.topVC().present(vc, animated: true)
+    }
+    @objc func transferGroup2(){
+
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberModel.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberModel.swift
new file mode 100644 (file)
index 0000000..6552a48
--- /dev/null
@@ -0,0 +1,21 @@
+//
+//  GroupMemberModel.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/12.
+//
+
+import HandyJSON
+
+class GroupMemberModelRoot:ResponseBaseModel {
+    var data = [GroupMemberModel]()
+}
+
+class GroupMemberModel:HandyJSON {
+    var userId = ""/// 用户ID
+    var userName = "" /// 用户名
+    var role:GroupRoleType = .bjy /// 角色名
+    var avatar = "" /// 头像
+
+    required public init() { }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Member/GroupMemberVC.swift
new file mode 100644 (file)
index 0000000..74b7740
--- /dev/null
@@ -0,0 +1,114 @@
+//
+//  GroupMemberVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/12.
+//
+
+import UIKit
+
+class GroupMemberVC: BaseVC{
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list:[GroupMemberModel] = []
+    var currentType = GroupRoleType.none
+    let btn = CustomButton()
+    var groupId = ""
+    
+    
+    init(groupId:String) {
+        self.groupId = groupId
+        super.init()
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+    }
+    override func setupViews() {
+        navBar.do { v in
+            v.title = "成员管理".l10n
+//            v.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+//            v.rightView.isHidden = false
+        }
+        
+        tableView.do { v in
+            v.backgroundColor = .clear
+            v.delegate = self
+            v.dataSource = self
+            v.rowHeight = 68
+            v.separatorStyle = .none
+            v.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0)
+            v.register(GroupMemberCell.self, forCellReuseIdentifier: GroupMemberCell.reuseID)
+            
+            let header = UIView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 40))
+            let label = UILabel(frame: CGRect(x: 18, y: 0, width: kScreenWid - 36, height: 40))
+            label.do { v in
+                v.text = "成员列表".l10n
+                v.font = .regular(14)
+                v.textColor = .color(.secondary)
+            }
+            header.addSubview(label)
+            v.tableHeaderView = header
+        }
+        view.addSubviews([tableView,btn])
+        
+        btn.set(title: "邀请".l10n, target: self, action: #selector(btnAction))
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.bottom.equalToSuperview()
+            make.top.equalTo(kNavBtm)
+        }
+        btn.snp.makeConstraints { make in
+            make.width.equalTo(272)
+            make.height.equalTo(46)
+            make.centerX.equalToSuperview()
+            make.bottom.equalToSuperview().inset(82)
+        }
+    }
+    override func setupData() {
+        let api = CommomApi.getGroupMembers(groupId: self.groupId)
+        HttpClient.request(api) {[weak self] (model:GroupMemberModelRoot,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                list = model.data
+            }
+            tableView.reloadData()
+            tableView.mj_header?.endRefreshing()
+        }
+    }
+    @objc func btnAction(){
+
+    }
+    override func navRightViewTapped() {
+
+    }
+}
+
+extension GroupMemberVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return list.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = GroupMemberCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.model = list[indexPath.row]
+        let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 68)
+        if indexPath.row == 0 {
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 8, height: 8))
+        }
+        else if indexPath.row == list.count - 1{
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 8, height: 8))
+        }
+        else{
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0))
+        }
+        if list.count == 1 {
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 8, height: 8))
+        }
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupCell.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupCell.swift
new file mode 100644 (file)
index 0000000..3174168
--- /dev/null
@@ -0,0 +1,90 @@
+//
+//  MyGroupCell.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+ class MyGroupCell: BaseTableViewCell {
+    let bgView = UIView()
+    let icon = UIImageView()
+    let nameLabel = UILabel()
+    let contentLabel = UILabel()
+    
+    var model:MyGroupModel!{
+        didSet{
+            if model.icon.isEmpty{
+                icon.image = UIImage(named: "group_avatar")
+            }
+            else{
+                icon.kf.setImage(with: model.icon.yc.asUrl)
+            }
+            nameLabel.text =  model.name
+            contentLabel.text = model.type.name + ".\(model.userCount)" + "人已加入".l10n
+        }
+    }
+    
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    override func setupViews() {
+        contentView.addSubview(bgView)
+        bgView.addSubviews([icon,nameLabel,contentLabel])
+        
+        backgroundColor = .color(.bgColor)
+        contentView.backgroundColor = .clear
+        selectionStyle = .none
+        bgView.do { v in
+            v.backgroundColor = .color(.cellBg)
+            v.yc.setCornerRadius(8)
+        }
+        nameLabel.do { (v) in
+            v.textColor = .color(.first)
+            v.font = .medium(14)
+            v.textAlignment = .left
+            v.lineBreakMode = .byTruncatingTail
+        }
+        contentLabel.do { v in
+            v.textColor = .color(.secondary)
+            v.font = .regular(11)
+            v.textAlignment = .left
+            v.lineBreakMode = .byTruncatingTail
+        }
+
+        icon.do { v in
+            v.yc.setCornerRadius(8)
+        }
+    }
+    
+    override func setupLayout() {
+        bgView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview().inset(18)
+            m.top.equalToSuperview().inset(6)
+            m.bottom.equalToSuperview()
+        }
+        
+        icon.snp.makeConstraints { (m) in
+            m.size.equalTo(36)
+            m.left.equalToSuperview().inset(15)
+            m.top.equalToSuperview().inset(15)
+        }
+        nameLabel.snp.makeConstraints { (m) in
+            m.top.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(60)
+            m.right.lessThanOrEqualToSuperview().inset(16)
+        }
+        contentLabel.snp.makeConstraints { (m) in
+            m.bottom.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(60)
+            m.right.lessThanOrEqualToSuperview().inset(16)
+        }
+
+    }
+    
+    
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupModel.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupModel.swift
new file mode 100644 (file)
index 0000000..703df34
--- /dev/null
@@ -0,0 +1,31 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+import HandyJSON
+
+class MyGroupModelRoot:ResponseBaseModel {
+    var data = [MyGroupModel]()
+}
+
+class MyGroupModel:HandyJSON {
+    var id = "" // 团队ID
+    var userId = ""// 用户ID,创建者的ID
+    var creatorNickname = "" // 创建者昵称
+    var creatorAvatar = "" // https://example.com/avatar.jpg", // 创建者头像
+    var icon = "" // https://example.com/icon.jpg", // 图标
+    var state = "" // 状态:1正常,0解散
+    var name = "" // 团队名称
+    var type:SportType = SportType.walk // 运动类型:running | cycling | walking
+    var introduction = "" // 团队简介
+    var joinReview = ""// 1加入审核,0不需要审核
+    var userCount = 0 //加入人数
+    var totalDistance = "" // 总距离,单位米
+    var totalTime = "" // 总运动时长,单位秒
+    var createTime = "" // 创建时间
+    var updateTime = ""// 更新时间
+    required public init() { }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupVC.swift
new file mode 100644 (file)
index 0000000..4b34e04
--- /dev/null
@@ -0,0 +1,118 @@
+//
+//  MyGroupVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+import JXSegmentedView
+
+
+enum MyGroupType{
+    case create
+    case add
+    
+    var name:String{
+        switch self {
+        case .create:
+            return "我管理的"
+        case .add:
+            return "我加入的"
+        }
+    }
+    var value:String{
+        switch self {
+        case .create:
+            return "1"
+        case .add:
+            return "0"
+        }
+    }
+    static var all:[MyGroupType] = [.create,.add]
+}
+
+class MyGroupVC: BaseVC {
+    private let segmentedDataSource = JXSegmentedTitleDataSource()
+    private let segmentedView = JXSegmentedView()
+    private lazy var listContainerView: JXSegmentedListContainerView! = {
+        let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView)
+        return view
+    }()
+    
+    var list:[MyGroupType] = MyGroupType.all
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        //        AudioPlayView.shared.show()
+        self.view.addSubview(AudioPlayView.shared)
+        MapManager.shared.setupSdk()
+    }
+    
+    override func setupLayout(){
+        
+        segmentedView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview()
+            m.height.equalTo(44.fitR)
+            m.top.equalToSuperview().inset(kNavBtm)
+        }
+        listContainerView.snp.makeConstraints { (m) in
+            m.width.left.equalToSuperview()
+            m.top.equalTo(segmentedView.snp.bottom)
+            m.bottom.equalToSuperview()
+        }
+    }
+    override func setupViews(){
+        navBar.do { v in
+            v.title = "我的团队"
+        }
+        view.addSubview(segmentedView)
+        view.addSubview(listContainerView)
+        
+        view.backgroundColor = .color(.bgColor)
+        segmentedView.do { (v) in
+            segmentedDataSource.titleNormalColor = .color(.secondary)
+            segmentedDataSource.titleSelectedColor = .color(.first)
+            segmentedDataSource.titleNormalFont = .regular(14)
+            segmentedDataSource.titleSelectedFont = .bold(16)
+            segmentedDataSource.isTitleZoomEnabled = true
+            // segmentedDataSource.titleSelectedZoomScale = 1
+            segmentedDataSource.isTitleStrokeWidthEnabled = true
+            segmentedDataSource.isItemSpacingAverageEnabled = true
+            segmentedDataSource.isTitleColorGradientEnabled = true
+            segmentedDataSource.titles = list.map{"\($0.name)"}
+            segmentedView.backgroundColor = .clear
+            
+            //配置指示器
+            let indicator = JXSegmentedIndicatorLineView()
+            indicator.indicatorWidth = 20
+            indicator.indicatorHeight = 4
+            indicator.verticalOffset = 6
+            indicator.indicatorColor = .color(.theme)
+            segmentedView.indicators = [indicator]
+            
+            v.dataSource = segmentedDataSource
+            v.listContainer = listContainerView
+        }
+    }
+}
+
+extension MyGroupVC: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return view
+    }
+}
+
+extension MyGroupVC: JXSegmentedListContainerViewDataSource {
+    func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
+        if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource {
+            return titleDataSource.dataSource.count
+        }
+        return 0
+    }
+    
+    func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
+        let type = list[index]
+        return MyGroupView(selectType: type)
+        
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/MyGroup/MyGroupView.swift
new file mode 100644 (file)
index 0000000..8cb5dea
--- /dev/null
@@ -0,0 +1,84 @@
+//
+//  GroupListView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+import UIKit
+import JXSegmentedView
+import MJRefresh
+
+
+class MyGroupView: BaseView,UITableViewDelegate,UITableViewDataSource {
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list = [MyGroupModel]()
+    var type = MyGroupType.add
+    
+    init(selectType type:MyGroupType){
+        super.init(frame: .zero)
+        self.type = type
+        tableView.mj_header?.beginRefreshing()
+    }
+    
+    override func setupViews() {
+        addSubview(tableView)
+        tableView.do { v in
+            v.backgroundColor = .color(.bgColor)
+            v.delegate = self
+            v.dataSource = self
+            v.rowHeight = 74
+            v.separatorStyle = .none
+            v.register(MyGroupCell.self, forCellReuseIdentifier: MyGroupCell.reuseID)
+            v.mj_header = MJRefreshNormalHeader(refreshingTarget: self, refreshingAction: #selector(refreshAction))
+        }
+    }
+    
+    override func setupLayout() {
+        tableView.snp.makeConstraints { (m) in
+            m.top.equalToSuperview().inset(10)
+            m.left.right.bottom.equalToSuperview()
+        }
+    }
+    
+    @objc func refreshAction(){
+        var params:[String:Any] = [:]
+        params["type"] = type.value
+        
+        let api = CommomApi.getSportGroupList(param: params)
+        HttpClient.request(api) {[weak self] (model:MyGroupModelRoot,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                list = model.data
+            }
+            tableView.reloadData()
+            tableView.mj_header?.endRefreshing()
+        }
+
+    }
+
+    
+    @MainActor required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+            return list.count
+        }
+        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+            let cell = MyGroupCell.cell(withTableView: tableView, indexPath: indexPath)
+            cell.model = list[indexPath.row]
+            return cell
+        }
+        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+            let vc = GroupDetailVC(groupId: list[indexPath.row].id)
+            App.shared.goToPage(vc)
+        }
+
+}
+
+extension MyGroupView: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return self
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupRoleVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupRoleVC.swift
new file mode 100644 (file)
index 0000000..613860a
--- /dev/null
@@ -0,0 +1,241 @@
+import UIKit
+import HandyJSON
+
+enum GroupRoleType:String,HandyJSONEnum{
+    case none = "none"
+    case pfs = "pfs"
+    case lpz = "lpz"
+    case gpz = "gpz"
+    case ccs = "ccs"
+    case psy = "psy"
+    case bjy = "bjy"
+    case qns = "qns"
+    case lqz = "lqz"
+    case gqz = "gqz"
+    case pps = "pps"
+    
+    
+    var name:String{
+        switch self {
+        case .none:
+            return "无"
+        case .pfs:
+            return "破风手"
+        case .lpz:
+            return "领跑者"
+        case .gpz:
+            return "跟跑者"
+        case .ccs:
+            return "冲刺手"
+        case .psy:
+            return "配速员"
+        case .bjy:
+            return "补给员"
+        case .qns:
+            return "全能手"
+        case .lqz:
+            return "领骑者"
+        case .gqz:
+            return "跟骑者"
+        case .pps:
+            return "爬坡手"
+        }
+    }
+    var color:UIColor{
+        switch self {
+        case .none:
+            return .rgb(0xD9D9D9)
+        case .pfs:
+            return .rgb(0xFF9232)
+        case .lpz:
+            return .rgb(0x7C2CED)
+        case .gpz:
+            return .rgb(0x21B7F8)
+        case .ccs:
+            return .rgb(0xFF583B)
+        case .psy:
+            return .rgb(0x4252FF)
+        case .bjy:
+            return .rgb(0x13D598)
+        case .qns:
+            return .rgb(0xF1BF18)
+        case .lqz:
+            return .rgb(0x7C2CED)
+        case .gqz:
+            return .rgb(0xF1BF18)
+        case .pps:
+            return .rgb(0xF1BF18)
+        }
+    }
+    
+    static var all:[GroupRoleType] = [.none,.pfs,.lpz,.gpz,.ccs,.psy,.bjy,.qns]
+}
+
+class GroupRoleVC: BaseVC{
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list:[GroupRoleType] = GroupRoleType.all
+    var currentType = GroupRoleType.none
+    let btn = CustomButton()
+    var didSelected:((GroupRoleType)->Void)?
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+    }
+    override func setupViews() {
+        navBar.do { v in
+            v.title = "我的团队角色".l10n
+            v.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+            v.rightView.isHidden = false
+        }
+        
+        tableView.do { v in
+            v.backgroundColor = .color(.bgColor)
+            v.delegate = self
+            v.dataSource = self
+            v.rowHeight = 52
+            v.separatorStyle = .none
+            v.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0)
+            v.register(RoleCell.self, forCellReuseIdentifier: RoleCell.reuseID)
+            
+            let header = UIView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 40))
+            let label = UILabel(frame: CGRect(x: 18, y: 0, width: kScreenWid - 36, height: 40))
+            label.do { v in
+                v.text = "我的团队-团队设置-骑行团队角色".l10n
+                v.font = .regular(14)
+                v.textColor = .color(.secondary)
+            }
+            header.addSubview(label)
+            v.tableHeaderView = header
+        }
+        view.addSubviews([tableView,btn])
+        
+        btn.set(title: "确定".l10n, target: self, action: #selector(btnAction))
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.bottom.equalToSuperview()
+            make.top.equalTo(kNavBtm)
+        }
+        btn.snp.makeConstraints { make in
+            make.width.equalTo(272)
+            make.height.equalTo(46)
+            make.centerX.equalToSuperview()
+            make.bottom.equalToSuperview().inset(82)
+        }
+    }
+    @objc func btnAction(){
+        didSelected?(currentType)
+        self.navigationController?.popViewController(animated: true)
+    }
+    override func navRightViewTapped() {
+        let tip = GroupDesPopView()
+        tip.titleLabel.text = "团队角色说明".l10n
+        tip.titleLabel1.text = "团队角色是什么?".l10n
+        tip.desLabel2.text = "团队运动中各角色是分工明确、协同发力的核心单元,通过精准配合最大化团队运动效果;".l10n
+        tip.titleLabel2.text = "团队角色说明".l10n
+        tip.desLabel2.text = "破风手:以 “挡风减阻” 为核心,为后方队友节省体能;\n领骑者:把控整体节奏,稳定配速避免失衡;\n跟骑者:借助风阻优势储备体力,为关键阶段蓄力;\n冲刺手:聚焦终点前爆发,锁定最终成绩;\n配速员:作为 “节奏标杆”,助力队友校准目标; \n补给员:保障物资供应,维持运动续航; \n全能手:灵活补位,适配破风、带速等多元需求。".l10n
+        tip.tipsIcon.image = UIImage(named: "role_des")
+        tip.show()
+    }
+}
+
+extension GroupRoleVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return list.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = RoleCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.titleLabel.text = list[indexPath.row].name
+        cell.type.backgroundColor = list[indexPath.row].color
+        let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 52)
+        if indexPath.row == 0 {
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 8, height: 8))
+        }
+        else if indexPath.row == list.count - 1{
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 8, height: 8))
+        }
+        else{
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0))
+        }
+        cell.selectedIcon.isHidden =  list[indexPath.row] != currentType
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let data = list[indexPath.row]
+        currentType = data
+        tableView.reloadData()
+    }
+}
+class RoleCell: BaseTableViewCell {
+    let bgView = UIView()
+    let type = UIView()
+    let titleLabel = UILabel()
+    let selectedIcon = UIImageView()
+    let line = UIView()
+    
+   
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    override func setupViews() {
+        contentView.addSubview(bgView)
+        bgView.addSubviews([type,titleLabel,selectedIcon,line])
+        
+        backgroundColor = .color(.bgColor)
+        contentView.backgroundColor = .clear
+        selectionStyle = .none
+        bgView.do { v in
+            v.backgroundColor = .color(.cellBg)
+        }
+        type.do { v in
+            v.yc.setCornerRadius(3)
+            v.backgroundColor = .red
+        }
+        titleLabel.do { (v) in
+            v.textColor = .color(.first)
+            v.font = .bold(14)
+            v.textAlignment = .left
+        }
+        
+        selectedIcon.image = UIImage(named: "cell_selected")
+        line.do { v in
+            v.backgroundColor = .color(.separateLine)
+        }
+    }
+    
+    override func setupLayout() {
+        bgView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview().inset(18)
+            m.top.equalToSuperview()
+            m.bottom.equalToSuperview()
+        }
+        type.snp.makeConstraints { make in
+            make.left.equalToSuperview().inset(16)
+            make.centerY.equalToSuperview()
+            make.width.equalTo(6)
+            make.height.equalTo(16)
+        }
+        selectedIcon.snp.makeConstraints { (m) in
+            m.width.equalTo(21)
+            m.height.equalTo(16)
+            m.right.equalToSuperview().inset(22)
+            m.centerY.equalToSuperview()
+        }
+        titleLabel.snp.makeConstraints { (m) in
+            m.centerY.equalToSuperview()
+            m.left.equalToSuperview().offset(32)
+            m.height.equalToSuperview()
+        }
+        line.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.bottom.equalToSuperview()
+            make.height.equalTo(0.4)
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupTypeVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/Other/GroupTypeVC.swift
new file mode 100644 (file)
index 0000000..bb7a0e6
--- /dev/null
@@ -0,0 +1,80 @@
+//
+//  GroupTypeVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+import UIKit
+
+class GroupTypeVC: BaseVC{
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list:[SportType] = SportType.all
+    var currentType = SportType.walk
+    let btn = CustomButton()
+    var didSelected:((SportType)->Void)?
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+    }
+    override func setupViews() {
+        self.navBar.title = "团队类型".l10n
+        tableView.do { v in
+            v.backgroundColor = .color(.bgColor)
+            v.delegate = self
+            v.dataSource = self
+            v.rowHeight = 52
+            v.separatorStyle = .none
+            v.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0)
+            v.register(SelectCell.self, forCellReuseIdentifier: SelectCell.reuseID)
+        }
+        view.addSubviews([tableView,btn])
+        
+        btn.set(title: "确定".l10n, target: self, action: #selector(btnAction))
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.bottom.equalToSuperview()
+            make.top.equalTo(kNavBtm)
+        }
+        btn.snp.makeConstraints { make in
+            make.width.equalTo(272)
+            make.height.equalTo(46)
+            make.centerX.equalToSuperview()
+            make.bottom.equalToSuperview().inset(82)
+        }
+    }
+    @objc func btnAction(){
+        didSelected?(currentType)
+        self.navigationController?.popViewController(animated: true)
+    }
+}
+
+extension GroupTypeVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return list.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = SelectCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.titleLabel.text = list[indexPath.row].name
+        let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 52)
+        if indexPath.row == 0 {
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 8, height: 8))
+        }
+        else if indexPath.row == list.count - 1{
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 8, height: 8))
+        }
+        else{
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0))
+        }
+        cell.selectedIcon.isHidden =  list[indexPath.row] != currentType
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let data = list[indexPath.row]
+        currentType = data
+        tableView.reloadData()
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Group/add/AddGroupVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Group/add/AddGroupVC.swift
new file mode 100644 (file)
index 0000000..a403111
--- /dev/null
@@ -0,0 +1,106 @@
+//
+//  AddGrpupVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/6.
+//
+
+import UIKit
+
+class AddGroupVC: BaseVC{
+    let nameView = SportInputItem()
+    let typeView = SportSelectItem()
+    let desView = SportInputItem()
+    let okBtn = CustomButton()
+    var sportType:GroupRoleType = .none
+    
+    override func setupViews() {
+        navBar.do { v in
+            v.title = "加入团队".l10n
+            v.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+            v.rightView.isHidden = false
+        }
+        view.addSubviews([nameView,typeView,desView,okBtn])
+        nameView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "团队号".l10n
+            v.textField.text = ""
+            v.textField.attributedPlaceholder = NSAttributedString(string: "请输入团队号".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)])
+            
+        }
+        typeView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "团队角色".l10n
+            v.detailLabel.text = sportType.name
+            v.yc.addTap(target: self, action: #selector(typeAction))
+        }
+        desView.do { v in
+            v.yc.setCornerRadius(12)
+            v.titleLabel.text = "打招呼内容".l10n
+            v.textField.attributedPlaceholder = NSAttributedString(string: "描述一下您的团".l10n, attributes: [NSAttributedString.Key.foregroundColor: UIColor.color(.placeholder)])
+            v.maxLength = 50
+        }
+        okBtn.set(title: "加入".l10n,style: .theme1, target: self, action: #selector(okAction))
+    }
+    override func setupLayout() {
+        nameView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalToSuperview().inset(kNavBtm + 20)
+            make.height.equalTo(51)
+        }
+
+        desView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(nameView.snp.bottom).offset(10)
+            make.height.equalTo(65)
+        }
+        typeView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(desView.snp.bottom).offset(10)
+            make.height.equalTo(51)
+        }
+        okBtn.snp.makeConstraints { make in
+            make.bottom.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview().inset(51)
+            make.height.equalTo(46)
+        }
+    }
+    override func navRightViewTapped() {
+        let tip = GroupDesPopView()
+        tip.show()
+    }
+    @objc func typeAction(){
+        let vc = GroupRoleVC()
+        vc.didSelected = {[weak self] value in
+            self?.sportType = value
+            self?.typeView.detailLabel.text = value.name
+        }
+        App.shared.goToPage(vc)
+    }
+    @objc func lightAction(){
+        App.shared.goToPage(LightSetVC())
+    }
+    @objc func okAction(){
+        if nameView.isEmpty  {
+            YLprogressHUD.showAutoHiden("团队名称不能为空".l10n)
+            return
+        }
+        
+        YLprogressHUD.showLoading()
+        var params:[String:Any] = [:]
+        params["id"] = nameView.value
+        params["requestText"] = desView.value
+
+        let api =  CommomApi.applyJoinGroup(param: params)
+        HttpClient.request(api){ (model:ResponseBaseModel, res) in
+            
+            if model.isSuccess{
+                YLprogressHUD.hidden()
+            }
+            else{
+                YLprogressHUD.showAutoHiden(model.msg)
+            }
+        }
+    }
+}
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/History/SportHistoryVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/History/SportHistoryVC.swift
new file mode 100644 (file)
index 0000000..d64dece
--- /dev/null
@@ -0,0 +1,124 @@
+//
+//  History.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/25.
+//
+
+import JXSegmentedView
+
+
+enum SportHistoryType{
+    case week
+    case month
+    case year
+    case all
+    
+    var name:String{
+        switch self {
+        case .week:
+            return "周"
+        case .month:
+            return "月"
+        case .year:
+            return "年"
+        case .all:
+            return "总"
+        }
+    }
+    static var allData:[SportHistoryType] = [.week,.month,.year,.all]
+}
+
+class SportHistoryVC: BaseVC {
+    private let segmentedDataSource = JXSegmentedTitleDataSource()
+    private let segmentedView = JXSegmentedView()
+    private lazy var listContainerView: JXSegmentedListContainerView! = {
+        let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView)
+        return view
+    }()
+    
+    var list:[SportHistoryType] = SportHistoryType.allData
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        //        AudioPlayView.shared.show()
+        self.view.addSubview(AudioPlayView.shared)
+        MapManager.shared.setupSdk()
+    }
+    
+    override func setupLayout(){
+        segmentedView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview()
+            m.height.equalTo(44.fitR)
+            m.top.equalToSuperview().inset(kNavBtm)
+        }
+        listContainerView.snp.makeConstraints { (m) in
+            m.width.left.equalToSuperview()
+            m.top.equalTo(segmentedView.snp.bottom)
+            m.bottom.equalToSuperview()
+        }
+    }
+    override func setupViews(){
+        navBar.do { v in
+            v.title = "运动记录"
+        }
+        view.addSubview(segmentedView)
+        view.addSubview(listContainerView)
+        
+        view.backgroundColor = .color(.bgColor)
+        segmentedView.do { (v) in
+            segmentedDataSource.titleNormalColor = .color(.secondary)
+            segmentedDataSource.titleSelectedColor = .color(.first)
+            segmentedDataSource.titleNormalFont = .regular(14)
+            segmentedDataSource.titleSelectedFont = .bold(16)
+            segmentedDataSource.isTitleZoomEnabled = true
+            // segmentedDataSource.titleSelectedZoomScale = 1
+            segmentedDataSource.isTitleStrokeWidthEnabled = true
+            segmentedDataSource.isItemSpacingAverageEnabled = true
+            segmentedDataSource.isTitleColorGradientEnabled = true
+            segmentedDataSource.titles = list.map{"\($0.name)"}
+            segmentedView.backgroundColor = .clear
+            
+            //配置指示器
+            let indicator = JXSegmentedIndicatorLineView()
+            indicator.indicatorWidth = 20
+            indicator.indicatorHeight = 4
+            indicator.verticalOffset = 6
+            indicator.indicatorColor = .color(.theme)
+            segmentedView.indicators = [indicator]
+            
+            v.dataSource = segmentedDataSource
+            v.listContainer = listContainerView
+        }
+    }
+}
+
+extension SportHistoryVC: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return view
+    }
+}
+
+extension SportHistoryVC: JXSegmentedListContainerViewDataSource {
+    func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
+        if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource {
+            return titleDataSource.dataSource.count
+        }
+        return 0
+    }
+    
+    func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
+        let type = list[index]
+        switch type {
+        case .week:
+            return SprotHistoryView()
+        case .month:
+            return SprotHistoryView()
+        case .year:
+            return SprotHistoryView()
+        case .all:
+            return SprotHistoryView()
+        }
+        
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/History/SprotHistoryView.swift b/ShenYin/ShenYin/UI/Sport/Sport/History/SprotHistoryView.swift
new file mode 100644 (file)
index 0000000..bd99048
--- /dev/null
@@ -0,0 +1,25 @@
+//
+//  SprotHistoryView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/25.
+//
+
+import UIKit
+import JXSegmentedView
+
+class SprotHistoryView: BaseView{
+    
+    override func setupViews() {
+       
+    }
+    override func setupLayout() {
+        
+    }
+}
+
+extension SprotHistoryView: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return self
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/BrightnessView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/BrightnessView.swift
new file mode 100644 (file)
index 0000000..48f4d23
--- /dev/null
@@ -0,0 +1,78 @@
+//
+//  LightView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/2.
+//
+
+extension LightSetVC{
+    class BrightnessView: BaseView {
+        let bgView = UIView()
+        let titleLabel = UILabel()
+        let btn = UIButton()
+        let line = UIView()
+        let slider = UISlider()
+        
+        override func setupViews() {
+            addSubviews([titleLabel,slider,btn,line])
+
+            titleLabel.do { (v) in
+                v.textColor = .white
+                v.font = .bold(14)
+                v.textAlignment = .left
+                v.text = "亮度".l10n
+            }
+            
+            btn.do { v in
+                v.setImage(UIImage(named: "tips_2"), for: .normal)
+                v.addTarget(self, action: #selector(tipsAction), for: .touchUpInside)
+            }
+            
+            line.do { v in
+                v.backgroundColor = .color(.separateLine)
+            }
+            slider.do { v in
+                //            v.backgroundColor = .red
+                v.minimumValue = 0
+                v.maximumValue = 100
+                v.value = 50
+                v.minimumTrackTintColor = .white
+                v.maximumTrackTintColor = .white.withAlphaComponent(0.2)
+                v.setThumbImage(UIColor.white.imageCircle(with:  CGSize(width: 20, height: 20)), for: .normal)
+                v.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged) // continuous changes
+                
+            }
+        }
+        
+        override func setupLayout() {
+            titleLabel.snp.makeConstraints { (m) in
+                m.top.equalToSuperview().inset(16)
+                m.left.equalToSuperview().offset(32)
+            }
+            slider.snp.makeConstraints { make in
+                make.height.equalTo(40)
+                make.centerX.equalToSuperview()
+                make.left.right.equalToSuperview().inset(32)
+                make.top.equalToSuperview().inset(40)
+            }
+            btn.snp.makeConstraints { (m) in
+                m.size.equalTo(30)
+                m.right.equalToSuperview().inset(32)
+                m.centerY.equalTo(titleLabel.snp.centerY)
+            }
+            
+            line.snp.makeConstraints { make in
+                make.left.right.equalToSuperview().inset(28)
+                make.bottom.equalToSuperview()
+                make.height.equalTo(0.4)
+            }
+        }
+        
+        @objc func tipsAction(){
+            SportPopView().show()
+        }
+        @objc func sliderChanged(_ slider:UISlider){
+            
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/LightSetVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/LightSetVC.swift
new file mode 100644 (file)
index 0000000..2fb42fd
--- /dev/null
@@ -0,0 +1,103 @@
+//
+//  LightSetVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/2.
+//
+
+import UIKit
+class LightSetVC: BaseVC{
+    let bgView = UIScrollView()
+    let icon = UIImageView()
+    let stateView = StateView()
+    let modelView = ModeView()
+    let brightnessView = BrightnessView()
+    let sysView = StateView()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+
+    }
+    open override var preferredStatusBarStyle: UIStatusBarStyle {
+       return .lightContent
+    }
+    override func setupViews() {
+        navBar.do { v in
+            v.title = "灯带设置".l10n
+            v.titleLabel.textColor = .white
+            v.backgroundColor = .clear
+            v.leftView.setImage(UIImage(named: "arrow_back_white"), for: .normal)
+        }
+        view.backgroundColor = .rgb(0x1F1F1F)
+        view.addSubviews([bgView])
+        bgView.addSubviews([icon,stateView,modelView,brightnessView,sysView])
+
+        bgView.do { v in
+            v.backgroundColor = .clear
+        }
+        icon.do { v in
+            v.image = UIImage(named: "guchuandao_icon")
+        }
+        stateView.do { v in
+            v.iSwitch.addTarget(self, action: #selector(stateAction), for: .valueChanged)
+            v.titleLabel.text = "灯带".l10n
+        }
+        modelView.do { v in
+        }
+        brightnessView.do { v in
+//            v.backgroundColor = .yellow
+        }
+        sysView.do { v in
+            v.line.isHidden = true
+            v.titleLabel.text = "同步至队员".l10n
+        }
+        stateAction()
+    }
+    override func setupLayout() {
+
+        bgView.snp.makeConstraints { make in
+            make.left.equalToSuperview()
+            make.width.equalTo(kScreenWid)
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.height.equalTo(kScreenHei - kNavBtm)
+        }
+        icon.snp.makeConstraints { make in
+            make.height.equalTo(130.fitR)
+            make.width.equalTo(220.fitR)
+            make.centerX.equalToSuperview()
+            make.top.equalToSuperview().inset(40)
+        }
+        stateView.snp.makeConstraints { make in
+            make.width.equalTo(kScreenWid)
+            make.height.equalTo(60)
+            make.top.equalTo(icon.snp.bottom).offset(35)
+            make.left.equalToSuperview()
+        }
+        modelView.snp.makeConstraints { make in
+            make.width.equalTo(kScreenWid)
+            make.height.equalTo(103)
+            make.top.equalTo(stateView.snp.bottom).offset(0)
+            make.left.equalToSuperview()
+        }
+        brightnessView.snp.makeConstraints { make in
+            make.width.equalTo(kScreenWid)
+            make.height.equalTo(90)
+            make.top.equalTo(modelView.snp.bottom).offset(0)
+            make.left.equalToSuperview()
+        }
+        sysView.snp.makeConstraints { make in
+            make.width.equalTo(kScreenWid)
+            make.height.equalTo(60)
+            make.top.equalTo(brightnessView.snp.bottom).offset(0)
+            make.left.equalToSuperview()
+            make.bottom.equalToSuperview()
+        }
+    }
+
+    @objc func stateAction(){
+        modelView.isHidden = !stateView.iSwitch.isOn
+        brightnessView.isHidden = !stateView.iSwitch.isOn
+        sysView.isHidden = !stateView.iSwitch.isOn
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/ModeView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/ModeView.swift
new file mode 100644 (file)
index 0000000..f6f5a06
--- /dev/null
@@ -0,0 +1,165 @@
+//
+//  ModeView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/2.
+//
+enum LightMode {
+    case normal
+    case breath
+    case flash
+    
+    var name:String{
+        switch self {
+        case .normal:
+            return "正常".l10n
+        case .breath:
+            return "呼吸".l10n
+        case .flash:
+            return "闪烁".l10n
+        }
+    }
+}
+extension LightSetVC{
+
+    
+    class ModeView: BaseView {
+        let titleLabel = UILabel()
+        let normalItem = SelectedTypeItem()
+        let breathItem = SelectedTypeItem()
+        let flashItem = SelectedTypeItem()
+        let line = UIView()
+        
+        var isOk:Bool{
+            return normalItem.isSelected || breathItem.isSelected || flashItem.isSelected
+        }
+        var mode:LightMode?{
+            if normalItem.isSelected {
+                return .normal
+            }
+            if breathItem.isSelected {
+                return .breath
+            }
+            if flashItem.isSelected {
+                return .flash
+            }
+            return nil
+        }
+
+        
+        override func setupViews() {
+            //        backgroundColor = .red
+            addSubviews([titleLabel,normalItem,breathItem,flashItem,line])
+            
+            titleLabel.do { v in
+                v.textAlignment = .center
+                v.font = .bold(14)
+                v.textColor = .white
+                v.text = "模式".l10n
+            }
+            normalItem.do { v in
+                v.mode = .normal
+                v.yc.setCornerRadius(8)
+                v.yc.addTap(target: self, action: #selector(normalAction))
+                v.isSelected = true
+            }
+            breathItem.do { v in
+                v.mode = .breath
+                v.yc.setCornerRadius(8)
+                v.yc.addTap(target: self, action: #selector(breathAction))
+            }
+            flashItem.do { v in
+                v.mode = .flash
+                v.yc.setCornerRadius(8)
+                v.yc.addTap(target: self, action: #selector(flashAction))
+            }
+            line.do { v in
+                v.backgroundColor = .color(.separateLine)
+            }
+        }
+        
+        override func setupLayout() {
+            titleLabel.snp.makeConstraints { make in
+                make.top.equalToSuperview().inset(20)
+                make.left.equalToSuperview().inset(32)
+            }
+            normalItem.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(32)
+                make.top.equalTo(titleLabel.snp.bottom).offset(10)
+                make.width.equalTo(80.fitR)
+                make.height.equalTo(32)
+            }
+            breathItem.snp.makeConstraints { make in
+                make.centerX.equalToSuperview()
+                make.top.width.height.equalTo(normalItem)
+            }
+            flashItem.snp.makeConstraints { make in
+                make.right.equalToSuperview().inset(32)
+                make.top.width.height.equalTo(normalItem)
+            }
+            line.snp.makeConstraints { make in
+                make.left.right.equalToSuperview().inset(28)
+                make.bottom.equalToSuperview()
+                make.height.equalTo(0.4)
+            }
+        }
+        
+        @objc func normalAction(){
+            normalItem.isSelected = true
+            breathItem.isSelected = false
+            flashItem.isSelected = false
+        }
+        @objc func breathAction(){
+            normalItem.isSelected = false
+            breathItem.isSelected = true
+            flashItem.isSelected = false
+        }
+        @objc func flashAction(){
+            normalItem.isSelected = false
+            breathItem.isSelected = false
+            flashItem.isSelected = true
+        }
+        
+        
+        class SelectedTypeItem: BaseView {
+            let titleLabel = UILabel()
+            var mode:LightMode = .normal{
+                didSet{
+                    titleLabel.text = mode.name
+                }
+            }
+            
+            var isSelected:Bool = false{
+                didSet{
+                    if isSelected {
+                        titleLabel.textColor = .color(.first)
+                        backgroundColor = .color(.bgColor)
+                    }
+                    else{
+                        backgroundColor = .clear
+                        titleLabel.textColor = .color(.secondary)
+                        yc.setBorder(width: 1, color: .color(.secondary))
+                    }
+                }
+            }
+            
+            override func setupViews() {
+                backgroundColor = .color(.bgColor)
+                addSubviews([titleLabel])
+                titleLabel.do { v in
+                    v.textAlignment = .center
+                    v.adjustsFontSizeToFitWidth = true
+                    v.font = .regular(14)
+                }
+                self.isSelected = false
+            }
+            
+            override func setupLayout() {
+                titleLabel.snp.makeConstraints { make in
+                    make.edges.equalToSuperview()
+                }
+            }
+        }
+    }
+
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Light/StateView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Light/StateView.swift
new file mode 100644 (file)
index 0000000..66a1d90
--- /dev/null
@@ -0,0 +1,52 @@
+//
+//  StateView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/2.
+//
+
+extension LightSetVC{
+    class StateView: BaseView {
+        let titleLabel = UILabel()
+        let iSwitch = UISwitch()
+        let line = UIView()
+        weak var delegate:SportSetCellDelegate?
+
+        override func setupViews() {
+            addSubviews([titleLabel,iSwitch,line])
+
+            titleLabel.do { (v) in
+                v.textColor = .white
+                v.font = .bold(14)
+                v.textAlignment = .left
+            }
+           
+            iSwitch.do { v in
+                //            v.backgroundColor = .red
+                v.isOn = true
+            }
+           
+            line.do { v in
+                v.backgroundColor = .color(.separateLine)
+            }
+        }
+        
+        override func setupLayout() {
+            titleLabel.snp.makeConstraints { (m) in
+                m.centerY.equalToSuperview()
+                m.left.equalToSuperview().offset(32)
+            }
+            
+            iSwitch.snp.makeConstraints { (m) in
+                m.right.equalToSuperview().inset(32)
+                m.centerY.equalToSuperview()
+            }
+            
+            line.snp.makeConstraints { make in
+                make.left.right.equalToSuperview().inset(28)
+                make.bottom.equalToSuperview()
+                make.height.equalTo(0.4)
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/CountdownView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/CountdownView.swift
new file mode 100644 (file)
index 0000000..5afe77e
--- /dev/null
@@ -0,0 +1,76 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+
+extension ReadyVC{
+    class CountdownView: BaseView {
+        let label = UILabel()
+        let skip = UILabel()
+        var timer:Timer!
+        var count = 3
+        
+        override func setupViews() {
+            addSubviews([label,skip])
+            label.do { v in
+                v.textColor = .color(.first)
+                v.font = .bold(210)
+                v.textAlignment = .center
+                v.text = "\(count)"
+            }
+            skip.do { v in
+                v.textColor = .color(.first)
+                v.font = .regular(14)
+                v.textAlignment = .center
+                v.text = "直接开始".l10n
+                v.yc.addTap(target: self, action: #selector(skipAction))
+
+                let line = UIView()
+                line.backgroundColor = .color(.first)
+                v.addSubview(line)
+                line.snp.makeConstraints { make in
+                    make.height.equalTo(1)
+                    make.left.right.equalToSuperview()
+                    make.bottom.equalToSuperview().offset(4)
+                }
+            }
+            backgroundColor = .white
+        }
+        
+        override func setupLayout() {
+            label.snp.makeConstraints { make in
+                make.center.equalToSuperview()
+            }
+            skip.snp.makeConstraints { make in
+                make.bottom.equalToSuperview().inset(100)
+                make.centerX.equalToSuperview()
+            }
+        }
+        override func setupData() {
+            timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
+        }
+        @objc func timerAction(){
+            count -= 1
+            label.text = "\(count)"
+            print(count)
+            if count == 0 {
+                skipAction()
+            }
+        }
+        static func show(in view:UIView){
+            let countView = CountdownView()
+            view.addSubview(countView)
+            countView.snp.makeConstraints { make in
+                make.edges.equalToSuperview()
+            }
+        }
+        @objc func skipAction(){
+            timer.invalidate()
+            App.shared.goToPage(SportingVC())
+            removeFromSuperview()
+        }
+    }
+    
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/CustomMapView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/CustomMapView.swift
new file mode 100644 (file)
index 0000000..fbc7567
--- /dev/null
@@ -0,0 +1,12 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+
+class CustomMapView: BaseView {
+    
+    
+    
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/DeviceStateView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/DeviceStateView.swift
new file mode 100644 (file)
index 0000000..06be847
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/25.
+//
+
+class DeviceStateView: BaseView {
+    let deviceLabel = UILabel()
+    let deviceBg = UIView()
+    let stateLabel = UIView()
+    
+
+    var value:String = ""{
+        didSet{
+            deviceLabel.text = value
+        }
+    }
+    
+    override func setupViews() {
+        addSubviews([deviceBg])
+        deviceBg.addSubviews([stateLabel,deviceLabel])
+        deviceBg.do { v in
+            v.backgroundColor = .rgb(0xF7F7F7 )
+            v.yc.setCornerRadius(13)
+        }
+        stateLabel.do { v in
+            v.backgroundColor = .rgb(0x2BD84E )
+            v.yc.setCornerRadius(2)
+        }
+        deviceLabel.do { v in
+            v.textColor = .color(.secondary)
+            v.font = .regular(10)
+            v.textAlignment = .center
+            v.text = "Deep Lite2 Ultra"
+        }
+    }
+    
+    override func setupLayout() {
+        deviceBg.snp.makeConstraints { make in
+            make.height.equalTo(26)
+            make.top.equalToSuperview()
+            make.left.right.equalToSuperview()
+        }
+        stateLabel.snp.makeConstraints { make in
+            make.size.equalTo(4)
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(9)
+        }
+        deviceLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(18)
+            make.right.equalToSuperview().inset(8)
+            make.height.equalToSuperview()
+        }
+    }
+    }
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/FooterView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/FooterView.swift
new file mode 100644 (file)
index 0000000..78b2034
--- /dev/null
@@ -0,0 +1,106 @@
+//
+//  FooterView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+
+extension ReadyVC{
+    class FooterView: BaseView {
+        let targetBtn = ButtonItem()
+        let routeBtn = ButtonItem()
+        let goBtn = UIImageView()
+        
+        override func setupViews() {
+            addSubviews([targetBtn,goBtn,routeBtn])
+            targetBtn.do { v in
+                v.titleLabel.text = "目标".l10n
+                v.icon.image = UIImage(named: "ready_target")
+                v.yc.setCornerRadius(18)
+                v.yc.addTap(target: self, action: #selector(targetAction))
+            }
+            goBtn.do { v in
+                v.image = UIImage(named: "ready_go")
+                v.yc.addTap(target: self, action: #selector(goAction))
+            }
+            routeBtn.do { v in
+                v.yc.setCornerRadius(18)
+                v.titleLabel.text = "路线".l10n
+                v.icon.image = UIImage(named: "ready_route")
+                v.yc.addTap(target: self, action: #selector(routeAction))
+            }
+        }
+        
+        override func setupLayout() {
+            goBtn.snp.makeConstraints { make in
+                make.size.equalTo(90)
+                make.centerY.equalToSuperview()
+                make.centerX.equalTo(kScreenWid/2)
+            }
+            
+            targetBtn.snp.makeConstraints { make in
+                make.width.equalTo(90)
+                make.height.equalTo(36)
+                make.centerY.equalToSuperview()
+                make.right.equalTo(goBtn.snp.left).offset(-27)
+            }
+            routeBtn.snp.makeConstraints { make in
+                make.width.equalTo(90)
+                make.height.equalTo(36)
+                make.centerY.equalToSuperview()
+                make.left.equalTo(goBtn.snp.right).offset(27)
+            }
+
+        }
+        
+        @objc func targetAction(){
+            App.shared.goToPage(SportTargetVC())
+        }
+        @objc func goAction(){
+            if false {
+                CustomAlert.showWith(title: "完善数据".l10n, messgage: "完善你的身体档案,让运动数据更准确。".l10n,ok: "前往".l10n) {
+                    
+                } okCallback: {
+                    App.shared.goToPage(UserInfoVC())
+                }
+            }
+            else{
+                CountdownView.show(in: App.shared.window!)
+            }
+            
+        }
+        @objc func routeAction(){
+            
+        }
+        
+        class ButtonItem: BaseView {
+            let icon = UIImageView()
+            let titleLabel = UILabel()
+
+            override func setupViews() {
+                addSubviews([titleLabel,icon])
+                icon.do { v in
+                }
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .regular(14)
+                }
+                backgroundColor = .white
+            }
+            
+            override func setupLayout() {
+                icon.snp.makeConstraints { make in
+                    make.size.equalTo(24)
+                    make.centerY.equalToSuperview()
+                    make.left.equalToSuperview().inset(16)
+                }
+                titleLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview()
+                    make.left.equalTo(43)
+                }
+            }
+        }
+
+    }
+    
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/Navbar.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/Navbar.swift
new file mode 100644 (file)
index 0000000..d076f38
--- /dev/null
@@ -0,0 +1,110 @@
+//
+//  Navbar.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import UIKit
+
+extension ReadyVC{
+    class Navbar: BaseView {
+        let backItem = BackItem()
+        let icon = UIImageView()
+        let groupBtn = UIButton()
+        let setBtn = UIButton()
+        
+        //
+        
+        override func setupViews() {
+            backgroundColor = .color(.bgColor)
+            addSubviews([backItem,icon,groupBtn,setBtn])
+            backItem.do { v in
+                v.titleLabel.text = SportManager.shared.sportType.name
+                v.yc.addTap(target: self, action: #selector(backAction))
+            }
+            icon.do { v in
+                v.image = UIImage(named: "sport_ai")
+            }
+            groupBtn.do { v in
+                v.setBackgroundImage(UIImage(named: "ready_group"), for: .normal)
+                v.setTitle("组队".l10n, for: .normal)
+                v.setTitleColor(.color(.first), for: .normal)
+                v.titleLabel?.font = .medium(14)
+                v.addTarget(self, action: #selector(groupAction), for: .touchUpInside)
+            }
+            setBtn.do { v in
+                v.setImage(UIImage(named: "ready_set"), for: .normal)
+                v.addTarget(self, action: #selector(groupAction), for: .touchUpInside)
+            }
+        }
+        
+        override func setupLayout() {
+            icon.snp.makeConstraints { make in
+                make.size.equalTo(32)
+                make.bottom.equalToSuperview().inset(10)
+                make.centerX.equalToSuperview()
+            }
+            setBtn.snp.makeConstraints { make in
+                make.width.equalTo(32)
+                make.height.equalTo(32)
+                make.centerY.equalTo(icon)
+                make.right.equalToSuperview().inset(10)
+            }
+            groupBtn.snp.makeConstraints { make in
+                make.width.equalTo(60)
+                make.height.equalTo(24)
+                make.centerY.equalTo(icon)
+                make.right.equalTo(setBtn.snp.left).offset(-10)
+            }
+            backItem.snp.makeConstraints { make in
+                make.left.equalToSuperview()
+                make.centerY.equalTo(icon)
+                make.height.equalTo(32)
+                make.width.equalTo(100)
+            }
+        }
+        @objc func backAction(){
+            App.shared.popPage()
+        }
+        @objc func groupAction(){
+            App.shared.goToPage(SportSetVC())
+        }
+        @objc func setAction(){
+            App.shared.goToPage(SportSetVC())
+        }
+        
+        class BackItem: BaseView {
+            let icon = UIImageView()
+            let titleLabel = UILabel()
+            
+            override func setupViews() {
+                addSubviews([titleLabel,icon])
+                icon.do { v in
+                    v.image = UIImage(named: "arrow_back")
+                }
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(16)
+                }
+            }
+            
+            override func setupLayout() {
+                icon.snp.makeConstraints { make in
+                    make.width.equalTo(10)
+                    make.height.equalTo(19)
+                    make.left.equalToSuperview().inset(18)
+                    make.centerY.equalToSuperview()
+                }
+                titleLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview()
+                    make.left.equalToSuperview().inset(42)
+                }
+            }
+        }
+    }
+
+    
+}
+
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyHeader.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyHeader.swift
new file mode 100644 (file)
index 0000000..a73da88
--- /dev/null
@@ -0,0 +1,107 @@
+//
+//  ReadyHeader.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+extension ReadyVC{
+    class ReadyHeader: BaseView {
+        
+        let typeLabel = UILabel()
+        let totalLabel = UILabel()
+        let unitLabel = UILabel()
+        let aiBtn = ButtonItem()
+
+        
+        override func setupViews() {
+            addSubviews([typeLabel,totalLabel,unitLabel,aiBtn])
+
+            typeLabel.do { v in
+                v.textColor = .color(.secondary)
+                v.font = .regular(12)
+                v.text = SportManager.shared.sportType.readyText
+                v.textAlignment = .left
+            }
+            totalLabel.do { v in
+                v.textColor = .color(.first)
+                v.font = .bold(20)
+                v.text = "00"
+                v.textAlignment = .left
+            }
+            unitLabel.do { v in
+                v.textColor = .color(.first)
+                v.font = .medium(14)
+                v.text = "公里"
+                v.textAlignment = .left
+            }
+            aiBtn.do { v in
+                v.yc.setBorder(width: 1, color: .color(.secondary), radius: 18)
+                v.yc.addTap(target: self, action: #selector(aiAction))
+            }
+        }
+        
+        override func setupLayout() {
+            typeLabel.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(32)
+                make.top.equalToSuperview().inset(25)
+                
+            }
+            totalLabel.snp.makeConstraints { make in
+                make.left.equalTo(typeLabel)
+                make.bottom.equalToSuperview().inset(27)
+                
+            }
+            unitLabel.snp.makeConstraints { make in
+                make.left.equalTo(totalLabel.snp.right).offset(2)
+                make.bottom.equalToSuperview().inset(30)
+                
+            }
+            
+            aiBtn.snp.makeConstraints { make in
+                make.size.equalTo(105)
+                make.height.equalTo(36)
+                make.centerY.equalToSuperview()
+                make.right.equalToSuperview().inset(18)
+            }
+        }
+        
+        @objc func aiAction(){
+//            App.shared.goToPage(SportTargetVC())
+        }
+
+        
+        class ButtonItem: BaseView {
+            let aiLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            override func setupViews() {
+                addSubviews([titleLabel,aiLabel])
+                aiLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .bold(14)
+                    v.text = "AI"
+                }
+                titleLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .regular(14)
+                    v.text = "语音陪练".l10n
+                }
+                backgroundColor = .white
+            }
+            
+            override func setupLayout() {
+                aiLabel.snp.makeConstraints { make in
+                    make.left.equalToSuperview().inset(12)
+                    make.centerY.equalToSuperview()
+                }
+                titleLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview()
+                    make.right.equalToSuperview().inset(12)
+                }
+            }
+        }
+       
+    }
+    
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Ready/ReadyVC.swift
new file mode 100644 (file)
index 0000000..a50f35c
--- /dev/null
@@ -0,0 +1,76 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+import MAMapKit
+
+class ReadyVC: BaseVC {
+    let bar = Navbar()
+    let headerView = ReadyHeader()
+    var mapView:MAMapView!
+    let footerView = FooterView()
+    let deviceView = DeviceStateView()
+    
+    override func setupViews() {
+        navBar.do { v in
+            v.addSubview(bar)
+        }
+        
+        mapView = MAMapView(frame: .zero)
+        mapView.do { v in
+            v.delegate = self
+            v.showsUserLocation = true
+            v.zoomLevel = 16
+            v.userTrackingMode = .followWithHeading
+        }
+        
+
+        view.addSubviews([headerView,mapView,footerView])
+        mapView.addSubview(deviceView)
+        
+        headerView.do { v in
+            v.backgroundColor = .clear
+        }
+        mapView.do { v in
+            v.yc.setCornerRadius(12)
+        }
+        footerView.do { v in
+        }
+    }
+    override func setupLayout() {
+        bar.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        headerView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview()
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.height.equalTo(100)
+        }
+        footerView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview()
+            make.bottom.equalToSuperview()
+            make.height.equalTo(200)
+        }
+        mapView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(headerView.snp.bottom)
+            make.bottom.equalTo(footerView.snp.top)
+        }
+        deviceView.snp.makeConstraints { make in
+            make.top.right.equalToSuperview().inset(12)
+            make.height.equalTo(26)
+        }
+    }
+    override func setupData() {
+        
+    }
+    
+}
+
+extension ReadyVC:MAMapViewDelegate{
+    func mapViewRequireLocationAuth(_ locationManager: CLLocationManager!) {
+//        locationManager.requestAlwaysAuthorization()
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/DataView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/DataView.swift
new file mode 100644 (file)
index 0000000..1b2c5c5
--- /dev/null
@@ -0,0 +1,136 @@
+//
+//  DataView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+
+extension SportRootView{
+    class DataView: BaseView {
+        let header = headItem()
+        let line = UIView()
+        let lastView = ItemView()
+        
+        override func setupViews() {
+            backgroundColor = .color(.cellBg)
+            addSubviews([header,lastView,line])
+            line.backgroundColor = .color(.separateLine)
+            header.do { v in
+                v.yc.addTap(target: self, action: #selector(allAction))
+            }
+            lastView.do { v in
+                v.yc.addTap(target: self, action: #selector(lastAction))
+            }
+        }
+        
+        override func setupLayout() {
+            header.snp.makeConstraints { make in
+                make.left.right.top.equalToSuperview()
+                make.height.equalTo(51)
+            }
+            lastView.snp.makeConstraints { make in
+                make.left.right.bottom.equalToSuperview()
+                make.height.equalTo(72)
+            }
+            line.snp.makeConstraints { make in
+                make.left.right.equalToSuperview().inset(14)
+                make.height.equalTo(1)
+                make.bottom.equalTo(header)
+            }
+        }
+        @objc func allAction(){
+            App.shared.goToPage(SportHistoryVC())
+        }
+        @objc func lastAction(){
+            App.shared.goToPage(SportDetailVC())
+
+        }
+        
+        class headItem: BaseView {
+            let icon = UIImageView()
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            override func setupViews() {
+                yc.setCornerRadius(12)
+                addSubviews([icon,titleLabel,detailLabel])
+                
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .medium(14)
+                    v.text = "运动数据".l10n
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .regular(12)
+                    v.text = "全部".l10n
+                }
+                icon.do { v in
+                    v.image = UIImage(named: "arrow_2")
+                }
+                backgroundColor = .white
+            }
+            
+            override func setupLayout() {
+                
+                titleLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview()
+                    make.left.equalToSuperview().inset(14)
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview()
+                    make.right.equalToSuperview().inset(27)
+                }
+                icon.snp.makeConstraints { make in
+                    make.width.equalTo(7)
+                    make.height.equalTo(13)
+                    make.centerY.equalToSuperview()
+                    make.right.equalToSuperview().inset(12)
+                }
+            }
+        }
+        
+        class ItemView: BaseView {
+            let icon = UIImageView()
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            override func setupViews() {
+                yc.setCornerRadius(12)
+                addSubviews([icon,titleLabel,detailLabel])
+                
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .medium(14)
+                    v.text = "户外跑步 6.21公里"
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .regular(10)
+                    v.text = "30分26秒"
+                }
+                icon.do { v in
+                    v.image = UIImage(named: "add_2")
+                }
+                backgroundColor = .white
+            }
+            
+            override func setupLayout() {
+                
+                titleLabel.snp.makeConstraints { make in
+                    make.top.equalToSuperview().inset(16)
+                    make.left.equalToSuperview().inset(64)
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.bottom.equalToSuperview().inset(16)
+                    make.left.equalTo(titleLabel)
+                }
+                icon.snp.makeConstraints { make in
+                    make.size.equalTo(40)
+                    make.centerY.equalToSuperview()
+                    make.left.equalToSuperview().inset(14)
+                }
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/GroupView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/GroupView.swift
new file mode 100644 (file)
index 0000000..620dc8a
--- /dev/null
@@ -0,0 +1,102 @@
+//
+//  GroupView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+
+extension SportRootView{
+    class GroupView: BaseView {
+        let creatGroup = ItemView()
+        let addGroup = ItemView()
+        let myGroup = ItemView()
+        
+        override func setupViews() {
+            addSubviews([creatGroup,addGroup,myGroup])
+            creatGroup.do { v in
+                v.titleLabel.text = "创建团队".l10n
+                v.detailLabel.text = "创建我的团队".l10n
+                v.yc.addTap(target: self, action: #selector(creatAction))
+            }
+            addGroup.do { v in
+                v.titleLabel.text = "加入团队".l10n
+                v.detailLabel.text = "加入已创建的团队".l10n
+                v.yc.addTap(target: self, action: #selector(joinAction))
+
+            }
+            myGroup.do { v in
+                v.titleLabel.text = "我的团队".l10n
+                v.detailLabel.text = "我创建和已加入的团队".l10n
+                v.icon.isHidden = true
+                v.yc.addTap(target: self, action: #selector(myAction))
+            }
+        }
+        
+        override func setupLayout() {
+            creatGroup.snp.makeConstraints { make in
+                make.left.top.right.equalToSuperview()
+                make.height.equalTo(65)
+            }
+            addGroup.snp.makeConstraints { make in
+                make.left.bottom.equalToSuperview()
+                make.height.equalTo(65)
+                make.width.equalTo((kScreenWid - 36 - 10)/2)
+            }
+            myGroup.snp.makeConstraints { make in
+                make.right.bottom.equalToSuperview()
+                make.height.equalTo(addGroup)
+                make.left.equalTo(addGroup.snp.right).offset(10)
+            }
+        }
+        @objc func creatAction(){
+            App.shared.goToPage(CreateGroupVC())
+        }
+        @objc func joinAction(){
+            App.shared.goToPage(AddGroupVC())
+        }
+        @objc func myAction(){
+            App.shared.goToPage(MyGroupVC())
+        }
+    }
+    class ItemView: BaseView {
+        let icon = UIImageView()
+        let detailLabel = UILabel()
+        let titleLabel = UILabel()
+        
+        override func setupViews() {
+            backgroundColor =  .color(.cellBg)
+            yc.setCornerRadius(12)
+            addSubviews([icon,titleLabel,detailLabel])
+            
+            titleLabel.do { v in
+                v.textColor = .color(.first)
+                v.font = .medium(14)
+            }
+            detailLabel.do { v in
+                v.textColor = .color(.secondary)
+                v.font = .regular(10)
+            }
+            icon.do { v in
+                v.image = UIImage(named: "add_2")
+            }
+            backgroundColor = .white
+        }
+        
+        override func setupLayout() {
+            
+            titleLabel.snp.makeConstraints { make in
+                make.top.equalToSuperview().inset(16)
+                make.left.equalToSuperview().inset(14)
+            }
+            detailLabel.snp.makeConstraints { make in
+                make.bottom.equalToSuperview().inset(16)
+                make.left.equalTo(titleLabel)
+            }
+            icon.snp.makeConstraints { make in
+                make.size.equalTo(36)
+                make.centerY.equalToSuperview()
+                make.right.equalToSuperview().inset(12)
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/PlanView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/PlanView.swift
new file mode 100644 (file)
index 0000000..d29ffed
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  PlanView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+
+extension SportRootView{
+    class PlanView: BaseView {
+        let icon = ItemView()
+        
+        override func setupViews() {
+            addSubviews([icon])
+            icon.do { v in
+                v.titleLabel.text = "智能运动计划建议".l10n
+                v.detailLabel.text = "AI规划个人定制运动计划".l10n
+            }
+        }
+        
+        override func setupLayout() {
+            icon.snp.makeConstraints { make in
+                make.edges.equalToSuperview()
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/RouteView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/RouteView.swift
new file mode 100644 (file)
index 0000000..dadc7a4
--- /dev/null
@@ -0,0 +1,98 @@
+//
+//  RouteView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+
+extension SportRootView{
+    class RouteView: BaseView {
+        let header = headItem()
+        let line = UIView()
+        let routeView1 = UIView()
+        let routeView2 = UIView()
+
+        
+        override func setupViews() {
+            backgroundColor = .color(.cellBg)
+            addSubviews([header,routeView1,routeView2,line])
+            line.backgroundColor = .color(.separateLine)
+            
+            routeView1.do { v in
+                v.backgroundColor = .lightGray
+                v.yc.setCornerRadius(12)
+            }
+            routeView2.do { v in
+                v.backgroundColor = .lightGray
+                v.yc.setCornerRadius(12)
+            }
+        }
+        
+        override func setupLayout() {
+            header.snp.makeConstraints { make in
+                make.left.right.top.equalToSuperview()
+                make.height.equalTo(51)
+            }
+            routeView1.snp.makeConstraints { make in
+                make.left.bottom.equalToSuperview().inset(14)
+                make.height.equalTo(96)
+                make.width.equalTo(((kScreenWid - 64 - 10)/2))
+            }
+            routeView2.snp.makeConstraints { make in
+                make.right.bottom.equalToSuperview().inset(14)
+                make.height.equalTo(96)
+                make.width.equalTo(((kScreenWid - 64 - 10)/2))
+            }
+            line.snp.makeConstraints { make in
+                make.left.right.equalToSuperview().inset(14)
+                make.height.equalTo(1)
+                make.bottom.equalTo(header)
+            }
+        }
+        class headItem: BaseView {
+            let icon = UIImageView()
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            override func setupViews() {
+                yc.setCornerRadius(12)
+                addSubviews([icon,titleLabel,detailLabel])
+                
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .medium(14)
+                    v.text = "路线探索".l10n
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .regular(12)
+                    v.text = "全部".l10n
+                }
+                icon.do { v in
+                    v.image = UIImage(named: "arrow_2")
+                }
+                backgroundColor = .white
+            }
+            
+            override func setupLayout() {
+                
+                titleLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview()
+                    make.left.equalToSuperview().inset(14)
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview()
+                    make.right.equalToSuperview().inset(27)
+                }
+                icon.snp.makeConstraints { make in
+                    make.width.equalTo(7)
+                    make.height.equalTo(13)
+                    make.centerY.equalToSuperview()
+                    make.right.equalToSuperview().inset(12)
+                }
+            }
+        }
+        
+       
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/SportRootView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/SportRootView.swift
new file mode 100644 (file)
index 0000000..29ea350
--- /dev/null
@@ -0,0 +1,66 @@
+//
+//  SportVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/20.
+//
+
+import UIKit
+import JXSegmentedView
+
+class SportRootView: BaseView{
+    let sportType = SportTypeView()
+    let dataView = DataView()
+    let groupView = GroupView()
+    let planView = PlanView()
+    let routeView = RouteView()
+    
+    override func setupViews() {
+        addSubviews([sportType,dataView,groupView,planView,routeView])
+        dataView.do { v in
+            v.yc.setCornerRadius(12)
+        }
+        groupView.do { v in
+            v.yc.setCornerRadius(12)
+        }
+        planView.do { v in
+            v.yc.setCornerRadius(12)
+        }
+        routeView.do { v in
+            v.yc.setCornerRadius(12)
+        }
+    }
+    override func setupLayout() {
+        sportType.snp.makeConstraints { make in
+            make.left.right.equalToSuperview()
+            make.top.equalToSuperview().inset(20)
+            make.height.equalTo(80)
+        }
+        dataView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(sportType.snp.bottom).offset(10)
+            make.height.equalTo(123)
+        }
+        groupView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(dataView.snp.bottom).offset(10)
+            make.height.equalTo(140)
+        }
+        planView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(groupView.snp.bottom).offset(10)
+            make.height.equalTo(65)
+        }
+        routeView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.top.equalTo(planView.snp.bottom).offset(10)
+            make.height.equalTo(180)
+        }
+    }
+}
+
+extension SportRootView: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return self
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Root/SportTypeView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Root/SportTypeView.swift
new file mode 100644 (file)
index 0000000..3ca5d5f
--- /dev/null
@@ -0,0 +1,134 @@
+//
+//  SportTypeView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/20.
+//
+import HandyJSON
+
+enum SportType:String,HandyJSONEnum{
+    case run = "running"
+    case ride = "cycling"
+    case walk = "walking"
+    
+    var name:String{
+        switch self {
+        case .run:
+            return "户外跑步"
+        case .ride:
+            return "户外骑行"
+        case .walk:
+            return "健走"
+        }
+    }
+    var icon:String{
+        switch self {
+        case .run:
+            return "sport_run"
+        case .ride:
+            return "sport_ride"
+        case .walk:
+            return "sport_walk"
+        }
+    }
+    var readyText:String{
+        switch self {
+        case .run:
+            return "累积跑步距离".l10n
+        case .ride:
+            return "累积骑行距离".l10n
+        case .walk:
+            return "累积健走距离".l10n
+        }
+    }
+    
+    static var all:[SportType] = [.run,.ride,.walk]
+}
+
+class SportTypeView: BaseView {
+    let run = SportTypeItem()
+    let ride = SportTypeItem()
+    let walk = SportTypeItem()
+
+    override func setupViews() {
+        addSubviews([run,ride,walk])
+        run.do { v in
+            v.type = .run
+            v.yc.addTap(target: self, action: #selector(runAction))
+        }
+        ride.do { v in
+            v.type = .ride
+            v.yc.addTap(target: self, action: #selector(rideAction))
+        }
+        walk.do { v in
+            v.type = .walk
+            v.yc.addTap(target: self, action: #selector(walkAction))
+        }
+    }
+    
+    override func setupLayout() {
+        run.snp.makeConstraints { make in
+            make.left.equalToSuperview().inset(38)
+            make.width.equalTo(50)
+            make.height.equalTo(80)
+            make.top.equalToSuperview()
+        }
+        ride.snp.makeConstraints { make in
+            make.left.equalTo(run.snp.right).offset(32)
+            make.width.equalTo(50)
+            make.height.equalTo(80)
+            make.top.equalToSuperview()
+        }
+        walk.snp.makeConstraints { make in
+            make.left.equalTo(ride.snp.right).offset(32)
+            make.width.equalTo(50)
+            make.height.equalTo(80)
+            make.top.equalToSuperview()
+        }
+    }
+    @objc func runAction(){
+        SportManager.shared.sportType = .run
+        App.shared.goToPage(ReadyVC())
+    }
+    @objc func rideAction(){
+        SportManager.shared.sportType = .ride
+        App.shared.goToPage(ReadyVC())
+    }
+    @objc func walkAction(){
+        SportManager.shared.sportType = .walk
+        App.shared.goToPage(ReadyVC())
+    }
+}
+
+class SportTypeItem: BaseView {
+    private let icon = UIImageView()
+    private let titleLabel = UILabel()
+    var type:SportType = .run{
+        didSet{
+            titleLabel.text = type.name
+            icon.image = UIImage(named: type.icon)
+        }
+    }
+    override func setupViews() {
+        addSubviews([titleLabel,icon])
+        icon.do { v in
+            
+        }
+        titleLabel.do { v in
+            v.textColor = .color(.first)
+            v.font = .regular(10)
+        }
+    }
+    
+    override func setupLayout() {
+        icon.snp.makeConstraints { make in
+            make.size.equalTo(50)
+            make.top.equalToSuperview()
+            make.centerX.equalToSuperview()
+        }
+        titleLabel.snp.makeConstraints { make in
+            make.top.equalTo(icon.snp.bottom).offset(5)
+            make.centerX.equalTo(icon)
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetCell.swift b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetCell.swift
new file mode 100644 (file)
index 0000000..ce6aeb3
--- /dev/null
@@ -0,0 +1,321 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/23.
+//
+
+
+public enum SportSetCellType: Int {
+    case light
+    case voiceReport
+    case lcReport
+    case lcReportRate
+    case xlfdReport
+    case guideReport
+    case guideReportSet
+    case metronome
+    case metronomeRate
+    case metronomeAudio
+    case background
+    case permisson
+    case realData
+    
+    var title:String{
+        switch self {
+        case .light:
+            return "灯带设置".l10n
+        case .voiceReport:
+            return "语音播报".l10n
+        case .lcReport:
+            return "里程播报".l10n
+        case .lcReportRate:
+            return "里程播报频次".l10n
+        case .xlfdReport:
+            return "训练分段播报".l10n
+        case .guideReport:
+            return "指导播报".l10n
+        case .guideReportSet:
+            return "指导语音设置".l10n
+        case .metronome:
+            return "节拍器".l10n
+        case .metronomeRate:
+            return "节拍器频率".l10n
+        case .metronomeAudio:
+            return "节拍器声音".l10n
+        case .background:
+            return "后台保护".l10n
+        case .permisson:
+            return "运动权限".l10n
+        case .realData:
+            return "运动实况".l10n
+        }
+    }
+    var detail:String{
+        switch self {
+        case .lcReport:
+            return "当前已跑里程播报".l10n
+        case .xlfdReport:
+            return "训练分段开始和结束提示音".l10n
+        case .guideReport:
+            return "跑步过程中的指导语音".l10n
+        case .realData:
+            return "组内成员基本运动信息实时显示".l10n
+        default:
+            return ""
+        }
+    }
+    var showSwitch:Bool{
+        switch self {
+        case .voiceReport,.lcReport,.xlfdReport,.guideReport,.metronome,.background,.realData:
+            return true
+        default:
+            return false
+        }
+    }
+}
+
+protocol SportSetCellDelegate:NSObject{
+    func sportSetCellSwitchAction(cell:SportSetCell)
+}
+class SportSetCell: BaseTableViewCell {
+    let bgView = UIView()
+    let titleLabel = UILabel()
+    let detailLabel = UILabel()
+    let valueLabel = UILabel()
+    let iSwitch = UISwitch()
+    let nextIcon = UIImageView(image: UIImage(named: "arrow_next"))
+    let line = UIView()
+    weak var delegate:SportSetCellDelegate?
+    var model:SportSetModel!{
+        didSet{
+            switch type {
+            case .light:
+                break
+            case .voiceReport:
+                iSwitch.isOn = model.voiceReport
+            case .lcReport:
+                iSwitch.isOn = model.lcReport
+                
+            case .lcReportRate:
+                valueLabel.text = model.lcReportRate
+            case .xlfdReport:
+                iSwitch.isOn = model.xlfdReport
+            case .guideReport:
+                iSwitch.isOn = model.guideReport
+            case .guideReportSet:
+                valueLabel.text = model.guideReportSet
+            case .metronome:
+                iSwitch.isOn = model.metronome
+            case .metronomeRate:
+                valueLabel.text = model.metronomeRate
+            case .metronomeAudio:
+                valueLabel.text = model.metronomeAudio
+            case .background:
+                iSwitch.isOn = model.background
+            case .permisson:
+                break
+            case .realData:
+                iSwitch.isOn = model.realData
+            }
+            
+            if type == .lcReport {
+                line.isHidden  = model.lcReport
+            }
+            else if type == .guideReport {
+                line.isHidden  = model.guideReport
+            }
+            else if type == .metronome {
+                line.isHidden  = model.metronome
+            }
+            else if type == .permisson || type == .realData || type == .metronomeRate{
+                line.isHidden = true
+            }
+            else{
+                line.isHidden = false
+            }
+        }
+    }
+    
+    var type:SportSetCellType = .light{
+        didSet{
+            titleLabel.text = type.title
+            detailLabel.text = type.detail
+            iSwitch.isHidden = !type.showSwitch
+            nextIcon.isHidden = !iSwitch.isHidden
+            
+            if type.detail.isEmpty {
+                titleLabel.snp.remakeConstraints { (m) in
+                    m.centerY.equalToSuperview()
+                    m.left.equalToSuperview().offset(16)
+                }
+            }
+            else{
+                titleLabel.snp.remakeConstraints { (m) in
+                    m.top.equalToSuperview().inset(16)
+                    m.left.equalToSuperview().offset(16)
+                }
+            }
+            
+            if type == .metronomeRate || type == .metronomeAudio {
+                valueLabel.isHidden = false
+            }
+            else{
+                valueLabel.isHidden = true
+            }
+        }
+    }
+
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    override func setupViews() {
+        contentView.addSubview(bgView)
+        bgView.addSubviews([titleLabel,detailLabel,iSwitch,nextIcon,valueLabel,line])
+        
+        backgroundColor = .color(.bgColor)
+        contentView.backgroundColor = .clear
+        selectionStyle = .none
+        bgView.do { v in
+            v.backgroundColor = .color(.cellBg)
+            v.yc.addTap(target: self, action: #selector(tapAction))
+        }
+        titleLabel.do { (v) in
+            v.textColor = .color(.first)
+            v.font = .bold(14)
+            v.textAlignment = .left
+        }
+        detailLabel.do { (v) in
+            v.textColor = .color(.secondary)
+            v.font = .regular(10)
+            v.textAlignment = .left
+        }
+        iSwitch.do { v in
+//            v.backgroundColor = .red
+            v.addTarget(self, action: #selector(swicthAction), for: .valueChanged)
+        }
+        valueLabel.do { v in
+            v.textColor = .color(.secondary)
+            v.font = .regular(14)
+            v.textAlignment = .right
+        }
+        line.do { v in
+            v.backgroundColor = .color(.separateLine)
+        }
+    }
+    
+    override func setupLayout() {
+        bgView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview().inset(18)
+            m.top.equalToSuperview()
+            m.bottom.equalToSuperview()
+        }
+        titleLabel.snp.makeConstraints { (m) in
+            m.top.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(16)
+        }
+        detailLabel.snp.makeConstraints { (m) in
+            m.bottom.equalToSuperview().inset(16)
+            m.left.equalToSuperview().offset(16)
+        }
+        iSwitch.snp.makeConstraints { (m) in
+            m.right.equalToSuperview().inset(15)
+            m.centerY.equalToSuperview()
+        }
+        nextIcon.snp.makeConstraints { (m) in
+            m.width.equalTo(8)
+            m.height.equalTo(12)
+            m.right.equalToSuperview().inset(15)
+            m.centerY.equalToSuperview()
+        }
+        valueLabel.snp.makeConstraints { (m) in
+            m.centerY.equalToSuperview()
+            m.right.equalToSuperview().inset(38)
+            m.height.equalToSuperview()
+            m.left.equalToSuperview().inset(90)
+        }
+        line.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.bottom.equalToSuperview()
+            make.height.equalTo(0.4)
+        }
+    }
+    @objc func tapAction(){
+        switch type {
+        case .light:
+            App.shared.goToPage(LightSetVC())
+        case .lcReportRate:
+            lcReportAction()
+            break
+        case .guideReportSet:
+            guideReportSetAction()
+            break
+        case .metronomeRate:
+            metronomeRateAction()
+            break
+        case .metronomeAudio:
+            metronomeAudioAction()
+            break
+        
+        case .background:
+            backgroundAction()
+            break
+        default:
+            break
+        }
+    }
+    func lcReportAction(){
+        let list = Array(stride(from: 500, to: 1500, by: 500)).map { String($0) + "m" }
+        let v = CustomPicker2(title: "选择播报间距".l10n, list:list )
+        v.selectedIndex = 50
+        v.show()
+        v.didSelected = {[weak self] (index,value) in
+            guard let self = self else { return  }
+            
+        }
+    }
+    func guideReportSetAction(){
+        let list = [
+            CustomPickerModel(title: "详细".l10n, detail: "1公里配速、总平均配速、1公里用时、总运动时长".l10n),
+            CustomPickerModel(title: "简洁".l10n, detail: "1公里配速、1公里用时".l10n),
+            CustomPickerModel(title: "极简".l10n, detail: "总距离、总时长".l10n)]
+        let v = CustomPicker4(title: "指导语音密度".l10n, list:list )
+        v.selectedIndex = 0
+        v.show()
+        v.didSelected = {[weak self] (index,value) in
+            guard let self = self else { return  }
+
+        }
+    }
+    func metronomeRateAction(){
+        let list = Array(stride(from: 100, to: 250, by: 1)).map { String($0) + "次/分钟".l10n }
+        let v = CustomPicker2(title: "节拍器频率".l10n, list:list )
+        v.selectedIndex = 50
+        v.show()
+        v.didSelected = {[weak self] (index,value) in
+            guard let self = self else { return  }
+            
+        }
+    }
+    func metronomeAudioAction(){
+        let list = ["默认".l10n,"清脆".l10n,"重击".l10n]
+        let v = CustomPicker2(title: "节拍器声音".l10n, list:list )
+        v.selectedIndex = 50
+        v.show()
+        v.didSelected = {[weak self] (index,value) in
+            guard let self = self else { return  }
+            
+        }
+    }
+    func backgroundAction(){
+
+    }
+    @objc func swicthAction(){
+        delegate?.sportSetCellSwitchAction(cell: self)
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetModel.swift b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetModel.swift
new file mode 100644 (file)
index 0000000..0310fa4
--- /dev/null
@@ -0,0 +1,22 @@
+//
+//  SportSetModel.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/30.
+//
+
+class SportSetModel{
+    var light = ""
+    var voiceReport = true
+    var lcReport = false
+    var lcReportRate = ""
+    var xlfdReport  = false
+    var guideReport = false
+    var guideReportSet = ""
+    var metronome  = false
+    var metronomeRate = ""
+    var metronomeAudio = ""
+    var background  = false
+    var permisson = ""
+    var realData  = false
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Set/SportSetVC.swift
new file mode 100644 (file)
index 0000000..4347064
--- /dev/null
@@ -0,0 +1,149 @@
+//
+//  SportSetVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/23.
+//,,
+
+
+import UIKit
+class SportSetVC: BaseVC{
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list:[[SportSetCellType]] = [[.light],[.voiceReport],[.lcReport],[.xlfdReport],[.guideReport],[.metronome],[.background],[.permisson],[.realData]]
+    let model = SportSetModel()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+    }
+    override func setupViews() {
+        self.navBar.title = "设置".l10n
+        tableView.do { v in
+            v.backgroundColor = .color(.bgColor)
+            v.delegate = self
+            v.dataSource = self
+            v.rowHeight = 65
+            v.sectionHeaderHeight = 52
+            v.separatorStyle = .none
+            v.register(SportSetCell.self, forCellReuseIdentifier: SportSetCell.reuseID)
+            v.register(CustomSectionHeader.self, forHeaderFooterViewReuseIdentifier: CustomSectionHeader.reuseID)
+            if #available(iOS 15.0, *) {
+                tableView.sectionHeaderTopPadding = 0
+            }
+        }
+        
+        view.addSubview(tableView)
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.bottom.equalToSuperview()
+            make.top.equalTo(kNavBtm)
+        }
+    }
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+//        CacheManager.shared.user.tokenIsValid(){[weak self] sucess in
+//            self?.header.updateUI()
+//        }
+    }
+}
+
+extension SportSetVC:UITableViewDelegate,UITableViewDataSource {
+    func numberOfSections(in tableView: UITableView) -> Int {
+        return list.count
+    }
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return list[section].count
+    }
+    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if section == list.count - 1 {
+            let view = CustomSectionHeader.header(withTableView: tableView)
+            view.titleLabel.text = "组队设置".l10n
+            view.titleLabel.textColor = .color(.secondary)
+            return view
+        }
+        else{
+            return nil
+        }
+    }
+    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        if section == list.count - 1 {
+            return 52
+        }
+        else{
+            return 0
+        }
+    }
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = SportSetCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.type = list[indexPath.section][indexPath.row]
+        cell.model = model
+        cell.delegate = self
+        let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 65)
+        if cell.type == .light {
+            cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 12, height: 12))
+        }
+        else {
+            if cell.type == .realData{
+                cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 12, height: 12))
+            }
+            else if cell.type == .permisson {
+                cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 12, height: 12))
+            }
+            else{
+                cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0))
+            }
+        }
+
+        return cell
+    }
+//    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+//        let data = list[indexPath.section][indexPath.row]
+//        switch data {
+//        case .light:
+//            
+//        default:
+//            break
+//        }
+//    }
+}
+extension SportSetVC:SportSetCellDelegate {
+    func sportSetCellSwitchAction(cell:SportSetCell){
+        let state = cell.iSwitch.isOn
+        switch cell.type {
+        case .voiceReport:
+            model.voiceReport = state
+            tableView.reloadData()
+            break
+        case .lcReport:
+            model.lcReport = state
+            list[2] = state ? [.lcReport,.lcReportRate]:[.lcReport]
+            tableView.reloadData()
+            break
+        case .xlfdReport:
+            model.xlfdReport = state
+            tableView.reloadData()
+            break
+        case .guideReport:
+            model.guideReport = state
+            list[4] = state ? [.guideReport,.guideReportSet]:[.guideReport]
+            tableView.reloadData()
+            break
+        case .metronome:
+            model.metronome = state
+            list[5] = state ? [.metronome,.metronomeRate,.metronomeAudio]:[.metronome]
+            tableView.reloadData()
+            break
+        case .background:
+            model.background = state
+            tableView.reloadData()
+            break
+        case .realData:
+            model.realData = state
+            tableView.reloadData()
+            break
+        default:
+            break
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/CadenceView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/CadenceView.swift
new file mode 100644 (file)
index 0000000..8e41a47
--- /dev/null
@@ -0,0 +1,7 @@
+//
+//  CadenceView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/ElevationView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/ElevationView.swift
new file mode 100644 (file)
index 0000000..d323eea
--- /dev/null
@@ -0,0 +1,7 @@
+//
+//  elevationView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/FeelView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/FeelView.swift
new file mode 100644 (file)
index 0000000..0d7a8ed
--- /dev/null
@@ -0,0 +1,57 @@
+//
+//  FeelView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
+class FeelView: BaseView {
+    let header = SportDetailTitleView()
+    
+    override func setupViews() {
+        backgroundColor = .white
+        addSubviews([header])
+        header.do { v in
+            v.titleLabel.text = "运动感受".l10n
+            v.icon.image = UIImage(named: "sport_feel")
+        }
+    }
+    
+    override func setupLayout() {
+        header.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.height.equalTo(55)
+            make.left.right.equalToSuperview()
+        }
+
+    }
+}
+class SportDetailTitleView: BaseView {
+    let icon = UIImageView()
+    let titleLabel = UILabel()
+    
+    override func setupViews() {
+        addSubviews([icon,titleLabel])
+        
+        titleLabel.do { v in
+            v.textColor = .color(.first)
+            v.font = .medium(16)
+        }
+        icon.do { v in
+            v.image = UIImage(named: "")
+        }
+    }
+    
+    override func setupLayout() {
+        
+        titleLabel.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(48)
+        }
+        icon.snp.makeConstraints { make in
+            make.size.equalTo(24)
+            make.centerY.equalToSuperview()
+            make.left.equalToSuperview().inset(18)
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PacePerView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PacePerView.swift
new file mode 100644 (file)
index 0000000..7945489
--- /dev/null
@@ -0,0 +1,7 @@
+//
+//  PacePerView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PaceView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/PaceView.swift
new file mode 100644 (file)
index 0000000..9c69b83
--- /dev/null
@@ -0,0 +1,7 @@
+//
+//  SpeepView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailBoard.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailBoard.swift
new file mode 100644 (file)
index 0000000..0b88799
--- /dev/null
@@ -0,0 +1,309 @@
+////
+////  Untitled.swift
+////  ShenYin
+////
+////  Created by liangshi on 2026/2/5.
+////
+//
+import UIKit
+
+extension SportDetailVC{
+    class SportDetailBoard: BaseView {
+        let topIcon = UIImageView()
+        let header = UILabel()
+        let distanceItem = DistanceItem()
+        let item1 = LabelItem()
+        let item2 = LabelItem()
+        let item3 = LabelItem()
+        let item4 = LabelItem()
+        let item5 = LabelItem()
+        let item6 = LabelItem()
+        let item7 = LabelItem()
+        let item8 = LabelItem()
+        let item9 = LabelItem()
+        func updateUI(){
+            if SportManager.shared.sportType == .walk{
+                header.do { v in
+                    v.text = "健走".l10n
+                }
+                distanceItem.do { v in
+                    v.title = SportManager.shared.distanceStr
+                }
+                item1.do { v in
+                    v.title = SportManager.shared.durationStr
+                    v.detail = "时长".l10n
+                }
+                item2.do { v in
+                    v.title = SportManager.shared.peisuStr
+                    v.detail = "平均配速".l10n
+                }
+                item3.do { v in
+                    v.title = SportManager.shared.calorieStr
+                    v.detail = "运动消耗".l10n
+                }
+                item4.do { v in
+                    v.title = SportManager.shared.bushuStr
+                    v.detail = "步数".l10n
+                }
+                item5.do { v in
+                    v.title = SportManager.shared.avgBuFuStr
+                    v.detail = "平均步幅".l10n
+                }
+                item6.do { v in
+                    v.title = SportManager.shared.avgBuPinStr
+                    v.detail = "平均步频".l10n
+                }
+                item7.do { v in
+                    v.title = SportManager.shared.upStr
+                    v.detail = "积累爬升".l10n
+                }
+                item8.do { v in
+                    v.title = SportManager.shared.downStr
+                    v.detail = "积累下降".l10n
+                }
+
+            }
+            else if  SportManager.shared.sportType == .run{
+                header.do { v in
+                    v.text = "户外跑步".l10n
+                }
+                distanceItem.do { v in
+                    v.title = SportManager.shared.distanceStr
+                }
+                item1.do { v in
+                    v.title = SportManager.shared.durationStr
+                    v.detail = "时长".l10n
+                }
+                item2.do { v in
+                    v.title = SportManager.shared.peisuStr
+                    v.detail = "平均配速".l10n
+                }
+                item3.do { v in
+                    v.title = SportManager.shared.calorieStr
+                    v.detail = "运动消耗".l10n
+                }
+                item4.do { v in
+                    v.title = SportManager.shared.bushuStr
+                    v.detail = "步数".l10n
+                }
+                item5.do { v in
+                    v.title = SportManager.shared.avgBuFuStr
+                    v.detail = "平均步幅".l10n
+                }
+                item6.do { v in
+                    v.title = SportManager.shared.avgBuPinStr
+                    v.detail = "平均步频".l10n
+                }
+                item7.do { v in
+                    v.title = SportManager.shared.upStr
+                    v.detail = "积累爬升".l10n
+                }
+                item8.do { v in
+                    v.title = SportManager.shared.downStr
+                    v.detail = "积累下降".l10n
+                }
+
+            }
+            else if  SportManager.shared.sportType == .ride{
+                header.do { v in
+                    v.text = "户外骑行".l10n
+                }
+                distanceItem.do { v in
+                    v.title = SportManager.shared.distanceStr
+                }
+                item1.do { v in
+                    v.title = SportManager.shared.durationStr
+                    v.detail = "时长".l10n
+                }
+                item2.do { v in
+                    v.title = SportManager.shared.peisuStr
+                    v.detail = "平均配速".l10n
+                }
+                item3.do { v in
+                    v.title = SportManager.shared.calorieStr
+                    v.detail = "运动消耗".l10n
+                }
+                item4.do { v in
+                    v.title = SportManager.shared.bushuStr
+                    v.detail = "步数".l10n
+                }
+                item5.do { v in
+                    v.title = SportManager.shared.avgBuFuStr
+                    v.detail = "平均步幅".l10n
+                }
+                item6.do { v in
+                    v.title = SportManager.shared.avgBuPinStr
+                    v.detail = "平均步频".l10n
+                }
+                item7.do { v in
+                    v.title = SportManager.shared.upStr
+                    v.detail = "积累爬升".l10n
+                }
+                item8.do { v in
+                    v.title = SportManager.shared.downStr
+                    v.detail = "积累下降".l10n
+                }
+
+            }
+        }
+        override func setupViews() {
+            backgroundColor = .white
+            addSubviews([topIcon,header,distanceItem,item1,item2,item3,item4,item5,item6,item7,item8,item9])
+            topIcon.do { v in
+                v.backgroundColor = .color(.secondary)
+                v.yc.setCornerRadius(12)
+            }
+            header.do { v in
+                v.font = .medium(16)
+                v.textColor = .color(.first)
+            }
+            item9.do { v in
+                v.isHidden = true
+            }
+            updateUI()
+        }
+        
+        override func setupLayout() {
+            topIcon.snp.makeConstraints { make in
+                make.width.equalTo(42)
+                make.height.equalTo(42)
+                make.top.equalToSuperview().inset(18)
+                make.right.equalToSuperview().inset(18)
+            }
+            header.snp.makeConstraints { make in
+                make.top.leading.equalToSuperview().inset(18)
+            }
+            distanceItem.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(18)
+                make.top.equalToSuperview().inset(50)
+//                make.height.equalTo(64)
+            }
+            item1.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(18)
+                make.top.equalToSuperview().inset(130)
+                make.height.equalTo(50)
+                make.width.equalTo(kScreenWid/3)
+            }
+            item2.snp.makeConstraints { make in
+                make.centerX.equalToSuperview().offset(20)
+                make.width.top.height.equalTo(item1)
+            }
+            item3.snp.makeConstraints { make in
+                make.right.equalToSuperview()
+                make.width.top.height.equalTo(item1)
+            }
+            item4.snp.makeConstraints { make in
+                make.top.equalToSuperview().inset(200)
+                make.width.left.height.equalTo(item1)
+            }
+            item5.snp.makeConstraints { make in
+                make.centerX.equalTo(item2)
+                make.width.top.height.equalTo(item4)
+            }
+            item6.snp.makeConstraints { make in
+                make.right.equalToSuperview()
+                make.width.height.equalTo(item1)
+                make.top.equalTo(item4)
+            }
+            item7.snp.makeConstraints { make in
+                make.top.equalToSuperview().inset(270)
+                make.width.left.height.equalTo(item1)
+            }
+            item8.snp.makeConstraints { make in
+                make.centerX.equalTo(item2)
+                make.width.top.height.equalTo(item7)
+            }
+            item9.snp.makeConstraints { make in
+                make.right.equalToSuperview()
+                make.width.height.equalTo(item1)
+                make.top.equalTo(item7)
+            }
+        }
+
+        class LabelItem: BaseView {
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            var title:String = ""{
+                didSet{
+                    titleLabel.text = title
+                }
+            }
+            var detail:String = ""{
+                didSet{
+                    detailLabel.text = detail
+                }
+            }
+            
+            override func setupViews() {
+                addSubviews([titleLabel,detailLabel])
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .semiBold(24)
+                    v.textAlignment = .left
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .regular(12)
+                    v.textAlignment = .left
+                }
+            }
+            
+            override func setupLayout() {
+                titleLabel.snp.makeConstraints { make in
+                    make.top.equalToSuperview().inset(0)
+                    make.left.right.equalToSuperview()
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.bottom.equalToSuperview().inset(0)
+                    make.left.right.equalToSuperview()
+                }
+            }
+        }
+        class DistanceItem: BaseView {
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            var title:String = ""{
+                didSet{
+                    titleLabel.text = title
+                }
+            }
+            var detail:String = ""{
+                didSet{
+                    detailLabel.text = detail
+                }
+            }
+            
+            override func setupViews() {
+                addSubviews([titleLabel,detailLabel])
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(48)
+                    v.textAlignment = .left
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(20)
+                    v.textAlignment = .left
+                    v.text = "公里".l10n
+                }
+            }
+            
+            override func setupLayout() {
+                titleLabel.snp.makeConstraints { make in
+                    make.top.equalToSuperview().inset(0)
+                    make.left.equalToSuperview()
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.bottom.equalTo(titleLabel.snp.bottom).inset(8)
+                    make.left.equalTo(titleLabel.snp.right).offset(10)
+                }
+            }
+        }
+    }
+    
+    
+    
+}
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/SportDetailVC.swift
new file mode 100644 (file)
index 0000000..3c758ef
--- /dev/null
@@ -0,0 +1,99 @@
+//
+//  SportDetailVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/5.
+//
+
+import MAMapKit
+
+class SportDetailVC: BaseVC {
+    let scrollView = UIScrollView()
+    var mapView:MAMapView!
+    let detailView = SportDetailBoard()
+    let pace = FeelView()
+    let pacePer = FeelView()
+    let cadence = FeelView()
+    let elevation = FeelView()
+    let feel = FeelView()
+    
+    
+    override func setupViews() {
+        MapManager.shared.setupSdk()
+        navBar.do { v in
+            v.title = "运动数据".l10n
+        }
+
+        
+        mapView = MAMapView(frame: .zero)
+        mapView.do { v in
+            v.delegate = self
+            v.showsUserLocation = true
+            v.zoomLevel = 16
+            v.userTrackingMode = .followWithHeading
+        }
+        view.addSubview(scrollView)
+        scrollView.addSubviews([mapView,detailView,pace,pacePer,cadence,elevation,feel])
+
+        scrollView.do { v in
+            
+        }
+        detailView.do { v in
+            v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: 460), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20))
+        }
+    }
+    override func setupLayout() {
+        scrollView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.left.equalToSuperview()
+            make.width.equalTo(kScreenWid)
+            make.height.equalTo(kScreenHei - kNavBtm)
+        }
+        mapView.snp.makeConstraints { make in
+            make.width.equalTo(kScreenWid)
+            make.left.top.equalToSuperview()
+            make.height.equalTo(300)
+        }
+        detailView.snp.makeConstraints { make in
+            make.left.equalToSuperview()
+            make.width.equalTo(kScreenWid)
+            make.top.equalTo(mapView.snp.bottom)
+            make.height.equalTo(342)
+        }
+        pace.snp.makeConstraints { make in
+            make.left.width.equalTo(detailView)
+            make.top.equalTo(detailView.snp.bottom)
+            make.height.equalTo(100)
+        }
+        pacePer.snp.makeConstraints { make in
+            make.left.width.equalTo(detailView)
+            make.top.equalTo(pace.snp.bottom)
+            make.height.equalTo(100)
+        }
+        cadence.snp.makeConstraints { make in
+            make.left.width.equalTo(detailView)
+            make.top.equalTo(pacePer.snp.bottom)
+            make.height.equalTo(100)
+        }
+        elevation.snp.makeConstraints { make in
+            make.left.width.equalTo(detailView)
+            make.top.equalTo(cadence.snp.bottom)
+            make.height.equalTo(100)
+        }
+        feel.snp.makeConstraints { make in
+            make.left.width.equalTo(detailView)
+            make.top.equalTo(elevation.snp.bottom)
+            make.height.equalTo(100)
+            make.bottom.equalToSuperview()
+        }
+    }
+    override func setupData() {
+        
+    }
+}
+
+extension SportDetailVC:MAMapViewDelegate{
+    func mapViewRequireLocationAuth(_ locationManager: CLLocationManager!) {
+        //        locationManager.requestAlwaysAuthorization()
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StepRateView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StepRateView.swift
new file mode 100644 (file)
index 0000000..7913a83
--- /dev/null
@@ -0,0 +1,7 @@
+//
+//  stepRateView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StrideLengthView.swift b/ShenYin/ShenYin/UI/Sport/Sport/SportDetail/StrideLengthView.swift
new file mode 100644 (file)
index 0000000..3266d83
--- /dev/null
@@ -0,0 +1,7 @@
+//
+//  strideLengthView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Sporting/DetailBoard.swift b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/DetailBoard.swift
new file mode 100644 (file)
index 0000000..74d6349
--- /dev/null
@@ -0,0 +1,377 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import UIKit
+
+extension SportingVC{
+    class DetailBoard: BaseView {
+        let topIcon = UIImageView()
+        let header = headerItem()
+        let distanceItem = DistanceItem()
+        let item1 = LabelItem()
+        let item2 = LabelItem()
+        let item3 = LabelItem()
+        let item4 = LabelItem()
+        let btnView = ButtonView()
+        func updateUI(){
+            if SportManager.shared.sportType == .walk{
+                header.do { v in
+                    v.title = "健走".l10n
+                }
+                distanceItem.do { v in
+                    v.title = SportManager.shared.distanceStr
+                }
+                item1.do { v in
+                    v.title =  SportManager.shared.durationStr
+                    v.detail = "时长".l10n
+                }
+                item2.do { v in
+                    v.title =  SportManager.shared.peisuStr
+                    v.detail = "配速".l10n
+                }
+                item3.do { v in
+                    v.title =  SportManager.shared.calorieStr
+                    v.detail = "卡路里".l10n
+                }
+                item4.do { v in
+                    v.title = SportManager.shared.bushuStr
+                    v.detail = "步数".l10n
+                }
+            }
+            else if  SportManager.shared.sportType == .run{
+                header.do { v in
+                    v.title = "户外跑步".l10n
+                }
+                distanceItem.do { v in
+                    v.title = SportManager.shared.distanceStr
+                }
+                item1.do { v in
+                    v.title = SportManager.shared.durationStr
+                    v.detail = "时长".l10n
+                }
+                item2.do { v in
+                    v.title = SportManager.shared.peisuStr
+                    v.detail = "配速".l10n
+                }
+                item3.do { v in
+                    v.title = SportManager.shared.calorieStr
+                    v.detail = "卡路里".l10n
+                }
+                item4.do { v in
+                    v.title = SportManager.shared.bushuStr
+                    v.detail = "步数".l10n
+                }
+            }
+            else if  SportManager.shared.sportType == .ride{
+                header.do { v in
+                    v.title = "户外骑行".l10n
+                }
+                distanceItem.do { v in
+                    v.title = SportManager.shared.distanceStr
+                }
+                item1.do { v in
+                    v.title = SportManager.shared.durationStr
+                    v.detail = "时长".l10n
+                }
+                item2.do { v in
+                    v.title = SportManager.shared.speedStr
+                    v.detail = "时速".l10n
+                }
+                item3.do { v in
+                    v.title = SportManager.shared.calorieStr
+                    v.detail = "卡路里".l10n
+                }
+                item4.do { v in
+
+                }
+            }
+        }
+        override func setupViews() {
+            backgroundColor = .white
+            addSubviews([topIcon,header,distanceItem,item1,item2,item3,item4,btnView])
+            topIcon.do { v in
+                v.backgroundColor = .color(.secondary)
+                v.yc.setCornerRadius(2)
+            }
+            updateUI()
+        }
+        
+        override func setupLayout() {
+            topIcon.snp.makeConstraints { make in
+                make.width.equalTo(50)
+                make.height.equalTo(4)
+                make.top.equalToSuperview().inset(6)
+                make.centerX.equalToSuperview()
+            }
+            header.snp.makeConstraints { make in
+                make.top.equalToSuperview().inset(28)
+                make.left.right.equalToSuperview()
+                make.height.equalTo(25)
+            }
+            distanceItem.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(18)
+                make.top.equalToSuperview().inset(100)
+                make.height.equalTo(50)
+            }
+            item1.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(18)
+                make.top.equalToSuperview().inset(180)
+                make.height.equalTo(50)
+            }
+            item2.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(kScreenWid/2 + 18)
+                make.top.equalTo(item1.snp.top)
+                make.height.equalTo(50)
+            }
+            item3.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(18)
+                make.top.equalToSuperview().inset(250)
+                make.height.equalTo(50)
+            }
+            item4.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(kScreenWid/2 + 18)
+                make.top.equalTo(item3.snp.top)
+                make.height.equalTo(50)
+            }
+            btnView.snp.makeConstraints { make in
+                make.left.right.bottom.equalToSuperview()
+                make.height.equalTo(140)
+            }
+        }
+        
+
+        class ButtonView: BaseView {
+            let okBtn = UIButton(type: .custom)
+            let lockBtn = UIButton(type: .custom)
+            let setBtn = UIButton(type: .custom)
+            
+            let lockedBtn = UIButton(type: .custom)
+            let stopBtn = UIButton(type: .custom)
+            let continueBtn = UIButton(type: .custom)
+            
+            override func setupViews() {
+//                backgroundColor = .red
+                addSubviews([okBtn,lockBtn,setBtn,lockedBtn,stopBtn,continueBtn])
+                okBtn.do { v in
+                    v.setImage(UIImage(named: "btn_pause"), for: .normal)
+                    v.addTarget(self, action: #selector(okAction), for: .touchUpInside)
+                }
+                lockBtn.do { v in
+                    v.setImage(UIImage(named: "btn_lock"), for: .normal)
+                    v.addTarget(self, action: #selector(lockAction), for: .touchUpInside)
+                }
+                setBtn.do { v in
+                    v.setImage(UIImage(named: "btn_set"), for: .normal)
+                    v.addTarget(self, action: #selector(setAction), for: .touchUpInside)
+                }
+                
+                lockedBtn.do { v in
+                    v.setImage(UIImage(named: "btn_locked"), for: .normal)
+                    v.addTarget(self, action: #selector(lockedAction), for: .touchUpInside)
+                    v.isHidden = true
+                }
+                stopBtn.do { v in
+                    v.setImage(UIImage(named: "btn_stop"), for: .normal)
+                    v.addTarget(self, action: #selector(stopAction), for: .touchUpInside)
+                    v.isHidden = true
+                }
+                continueBtn.do { v in
+                    v.setImage(UIImage(named: "btn_continue"), for: .normal)
+                    v.addTarget(self, action: #selector(continueAction), for: .touchUpInside)
+                    v.isHidden = true
+                }
+            }
+            
+            override func setupLayout() {
+                okBtn.snp.makeConstraints { make in
+                    make.centerX.equalToSuperview()
+                    make.bottom.equalToSuperview().inset(24)
+                    make.size.equalTo(90)
+                }
+                lockBtn.snp.makeConstraints { make in
+                    make.left.equalToSuperview().inset(46)
+                    make.centerY.equalTo(okBtn)
+                    make.size.equalTo(50)
+                }
+                setBtn.snp.makeConstraints { make in
+                    make.right.equalToSuperview().inset(46)
+                    make.centerY.equalTo(okBtn)
+                    make.size.equalTo(50)
+                }
+                
+                lockedBtn.snp.makeConstraints { make in
+                    make.centerX.equalToSuperview()
+                    make.bottom.equalToSuperview().inset(24)
+                    make.size.equalTo(90)
+                }
+                continueBtn.snp.makeConstraints { make in
+                    make.left.equalToSuperview().inset(55)
+                    make.bottom.equalToSuperview().inset(24)
+                    make.size.equalTo(90)
+                }
+                stopBtn.snp.makeConstraints { make in
+                    make.right.equalToSuperview().inset(55)
+                    make.bottom.equalToSuperview().inset(24)
+                    make.size.equalTo(90)
+                }
+            }
+            @objc func okAction(){
+                SportManager.shared.pause()
+                continueBtn.isHidden = false
+                stopBtn.isHidden = false
+                
+                okBtn.isHidden = true
+                setBtn.isHidden = true
+                lockBtn.isHidden = true
+            }
+            @objc func lockAction(){
+                okBtn.isHidden = true
+                setBtn.isHidden = true
+                lockBtn.isHidden = true
+                lockedBtn.isHidden = false
+            }
+            @objc func setAction(){
+                
+            }
+            @objc func lockedAction(){
+                okBtn.isHidden = false
+                setBtn.isHidden = false
+                lockBtn.isHidden = false
+                lockedBtn.isHidden = true
+            }
+            @objc func stopAction(){
+                SportManager.shared.stop()
+                App.shared.goToPage(SportDetailVC())
+            }
+            @objc func continueAction(){
+                SportManager.shared.resume()
+                okBtn.isHidden = false
+                setBtn.isHidden = false
+                lockBtn.isHidden = false
+                
+                continueBtn.isHidden = true
+                stopBtn.isHidden = true
+            }
+        }
+        class headerItem: BaseView {
+            let deviceView = DeviceStateView()
+            let titleLabel = UILabel()
+            
+            var title:String = ""{
+                didSet{
+                    titleLabel.text = title
+                }
+            }
+            override func setupViews() {
+                addSubviews([titleLabel,deviceView])
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(16)
+                    v.textAlignment = .left
+                }
+            }
+            
+            override func setupLayout() {
+                titleLabel.snp.makeConstraints { make in
+                    make.centerY.equalToSuperview().inset(0)
+                    make.left.equalToSuperview().inset(18)
+                }
+                deviceView.snp.makeConstraints { make in
+                    make.height.equalTo(26)
+                    make.left.equalToSuperview().inset(94)
+                    make.centerY.equalToSuperview()
+                }
+            }
+        }
+        
+        
+        class LabelItem: BaseView {
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            var title:String = ""{
+                didSet{
+                    titleLabel.text = title
+                }
+            }
+            var detail:String = ""{
+                didSet{
+                    detailLabel.text = detail
+                }
+            }
+            
+            override func setupViews() {
+                addSubviews([titleLabel,detailLabel])
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(24)
+                    v.textAlignment = .left
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .regular(12)
+                    v.textAlignment = .left
+                }
+            }
+            
+            override func setupLayout() {
+                titleLabel.snp.makeConstraints { make in
+                    make.top.equalToSuperview().inset(0)
+                    make.left.right.equalToSuperview()
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.bottom.equalToSuperview().inset(0)
+                    make.left.right.equalToSuperview()
+                }
+            }
+        }
+        class DistanceItem: BaseView {
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            var title:String = ""{
+                didSet{
+                    titleLabel.text = title
+                }
+            }
+            var detail:String = ""{
+                didSet{
+                    detailLabel.text = detail
+                }
+            }
+            
+            override func setupViews() {
+                addSubviews([titleLabel,detailLabel])
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(48)
+                    v.textAlignment = .left
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(20)
+                    v.textAlignment = .left
+                    v.text = "公里".l10n
+                }
+            }
+            
+            override func setupLayout() {
+                titleLabel.snp.makeConstraints { make in
+                    make.top.equalToSuperview().inset(0)
+                    make.left.equalToSuperview()
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.bottom.equalToSuperview().inset(0)
+                    make.left.equalTo(titleLabel.snp.right).offset(10)
+                }
+            }
+        }
+    }
+    
+    
+  
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SimpleBoard.swift b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SimpleBoard.swift
new file mode 100644 (file)
index 0000000..1fb9a22
--- /dev/null
@@ -0,0 +1,90 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import UIKit
+
+extension SportingVC{
+    class SimpleBoard: BaseView {
+        let topIcon = UIImageView()
+        let distanceItem = LabelItem()
+        let durationItem = LabelItem()
+        
+        func updateUI(){
+            distanceItem.titleLabel.text = SportManager.shared.distanceStr
+            durationItem.titleLabel.text = SportManager.shared.durationStr
+        }
+        
+        override func setupViews() {
+            backgroundColor = .white
+            addSubviews([topIcon,distanceItem,durationItem])
+            topIcon.do { v in
+                v.backgroundColor = .color(.secondary)
+                v.yc.setCornerRadius(2)
+            }
+            distanceItem.do { v in
+                v.titleLabel.text = "0"
+                v.detailLabel.text = "总距离".l10n
+            }
+            durationItem.do { v in
+                v.titleLabel.text = "00:00:00"
+                v.detailLabel.text = "总时长".l10n
+            }
+        }
+        
+        override func setupLayout() {
+            topIcon.snp.makeConstraints { make in
+                make.width.equalTo(50)
+                make.height.equalTo(4)
+                make.top.equalToSuperview().inset(6)
+                make.centerX.equalToSuperview()
+            }
+            distanceItem.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(18)
+                make.top.equalToSuperview().inset(27)
+                make.height.equalTo(50)
+            }
+            durationItem.snp.makeConstraints { make in
+                make.left.equalToSuperview().inset(kScreenWid/2 + 18)
+                make.top.equalToSuperview().inset(27)
+                make.height.equalTo(distanceItem)
+            }
+        }
+        
+        @objc func groupAction(){
+            App.shared.goToPage(SportSetVC())
+        }
+        class LabelItem: BaseView {
+            let detailLabel = UILabel()
+            let titleLabel = UILabel()
+            
+            override func setupViews() {
+                addSubviews([titleLabel,detailLabel])
+                titleLabel.do { v in
+                    v.textColor = .color(.first)
+                    v.font = .bold(24)
+                    v.textAlignment = .left
+                }
+                detailLabel.do { v in
+                    v.textColor = .color(.secondary)
+                    v.font = .regular(12)
+                    v.textAlignment = .left
+                }
+            }
+            
+            override func setupLayout() {
+                titleLabel.snp.makeConstraints { make in
+                    make.top.equalToSuperview().inset(0)
+                    make.left.right.equalToSuperview()
+                }
+                detailLabel.snp.makeConstraints { make in
+                    make.bottom.equalToSuperview().inset(0)
+                    make.left.right.equalToSuperview()
+                }
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SportingVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Sporting/SportingVC.swift
new file mode 100644 (file)
index 0000000..e5e808a
--- /dev/null
@@ -0,0 +1,164 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import MAMapKit
+
+class SportingVC: BaseVC {
+    var line: MAPolyline!
+    var mapView:MAMapView!
+    let simpleView = SimpleBoard()
+    let detailView = DetailBoard()
+
+    var trackOverlay: MAPolyline?
+    
+    override func setupViews() {
+        mapView = MAMapView(frame: .zero)
+        mapView.delegate = self
+        mapView.showsUserLocation = true
+        mapView.zoomLevel = 16
+        mapView.userTrackingMode = .followWithHeading
+//        mapView.desiredAccuracy = kCLLocationAccuracyBest
+        mapView.desiredAccuracy = kCLLocationAccuracyKilometer
+        mapView.distanceFilter = 0.001
+
+
+        view.addSubviews([mapView,simpleView,detailView])
+        mapView.do { v in
+            
+        }
+        simpleView.do { v in
+            v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: 100), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20))
+            v.yc.addTap(target: self, action: #selector(simpleTapAction))
+        }
+        detailView.do { v in
+            v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: 460), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20))
+            v.isHidden = true
+            v.yc.addTap(target: self, action: #selector(detailTapAction))
+        }
+    }
+    override func setupLayout() {
+        simpleView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview()
+            make.bottom.equalToSuperview()
+            make.height.equalTo(100)
+        }
+        detailView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview()
+            make.bottom.equalToSuperview()
+            make.height.equalTo(460)
+        }
+        mapView.snp.makeConstraints { make in
+            make.left.right.top.equalToSuperview()
+            make.bottom.equalTo(simpleView.snp.top).offset(20)
+        }
+    }
+    override func setupData() {
+        NotificationManager.add(self, selector: #selector(updateLocation(_:)), forName: .updateLocation)
+        SportManager.shared.start()
+    }
+    @objc func updateLocation(_ noti:Notification){
+        if let userLocation = mapView.userLocation {
+            let latitude = userLocation.location.coordinate.latitude
+            let longitude = userLocation.location.coordinate.longitude
+            let altitude = userLocation.location.altitude
+            let point = TrackPoint(latitude: latitude, longitude: longitude, altitude: altitude)
+            
+            
+            SportManager.shared.pointList.append(point)
+            updateUI()
+        }
+        
+        simpleView.updateUI()
+        detailView.updateUI()
+    }
+    @objc func simpleTapAction(){
+        simpleView.isHidden = true
+        detailView.isHidden = false
+        showDetail()
+    }
+    @objc func detailTapAction(){
+        simpleView.isHidden = false
+        detailView.isHidden = true
+        showSimple()
+    }
+    func showSimple(){
+        mapView.snp.remakeConstraints { make in
+            make.left.right.top.equalToSuperview()
+            make.bottom.equalTo(simpleView.snp.top).offset(20)
+        }
+    }
+    func showDetail(){
+        mapView.snp.remakeConstraints { make in
+            make.left.right.top.equalToSuperview()
+            make.bottom.equalTo(detailView.snp.top).offset(20)
+        }
+    }
+    func updateUI(){
+        // 移除旧轨迹
+        if let oldTrackOverlay = trackOverlay {
+            mapView.remove(oldTrackOverlay)
+        }
+        
+        var trackCoordinates = [CLLocationCoordinate2D]()
+        for point in SportManager.shared.pointList {
+            let coordinate = CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude)
+            trackCoordinates.append(coordinate)
+        }
+        
+
+        
+        // 只有当有足够的点时才绘制轨迹线
+        if trackCoordinates.count >= 2 {
+            let coordinatesPointer = UnsafeMutablePointer<CLLocationCoordinate2D>.allocate(capacity: trackCoordinates.count)
+            coordinatesPointer.initialize(from: trackCoordinates, count: trackCoordinates.count)
+            
+            // 创建新的轨迹线
+            let newTrackOverlay = MAPolyline(coordinates: coordinatesPointer, count: UInt(trackCoordinates.count))
+            
+            // 添加到地图
+            mapView.add(newTrackOverlay)
+            
+            // 保存引用
+            trackOverlay = newTrackOverlay
+            
+//            // 调整地图视野以适应轨迹
+//            mapView.showOverlays([newTrackOverlay], edgePadding: UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50), animated: true)
+        }
+    }
+}
+
+extension SportingVC:MAMapViewDelegate{
+    func mapViewRequireLocationAuth(_ locationManager: CLLocationManager!) {
+        //        locationManager.requestAlwaysAuthorization()
+    }
+    func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! {
+        
+        if (overlay.isKind(of: MAPolyline.self))
+        {
+            let renderer = MAPolylineRenderer(polyline: overlay as! MAPolyline)
+            renderer?.lineWidth = 8.0
+            
+            
+            return renderer;
+        }
+        
+        return nil;
+    }
+    
+    func mapView(_ mapView: MAMapView!, didUpdate userLocation: MAUserLocation!, updatingLocation: Bool) {
+        Logger.debug(userLocation.location)
+        
+//        let latitude = userLocation.location.coordinate.latitude
+//        let longitude = userLocation.location.coordinate.longitude
+//        let altitude = userLocation.location.altitude
+//        let point = TrackPoint(latitude: latitude, longitude: longitude, altitude: altitude)
+//        
+//        
+//        SportManager.shared.pointList.append(point)
+//        updateUI()
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/CalorieView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/CalorieView.swift
new file mode 100644 (file)
index 0000000..e3f5607
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  CaloriView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import JXSegmentedView
+
+extension SportTargetVC{
+    class CaloriView: BaseView ,JXSegmentedListContainerViewListDelegate{
+        let label = UILabel()
+        
+        override func setupViews() {
+            addSubviews([label])
+            label.do { v in
+                v.font = .bold(28)
+                v.textAlignment = .center
+                v.textColor = .color(.first)
+                v.text = "300 千卡"
+                v.yc.addTap(target: self, action: #selector(tapAction))
+            }
+        }
+        override func setupLayout() {
+            label.snp.makeConstraints { make in
+                make.left.right.equalToSuperview()
+                make.top.equalToSuperview().inset(50)
+            }
+        }
+        
+        func listView() -> UIView {
+            return self
+        }
+        @objc func tapAction(){
+            let list = Array(stride(from: 1, to: 1000, by: 1)).map { String($0) + " 千卡" }
+            let v = CustomPicker2(title: "卡路里".l10n, list:list )
+            v.selectedIndex = 0
+            v.show()
+            v.didSelected = {[weak self] (index,value) in
+                guard let self = self else { return  }
+                label.text = value
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/DistanceView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/DistanceView.swift
new file mode 100644 (file)
index 0000000..4aeadce
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  DistanceView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import JXSegmentedView
+
+extension SportTargetVC{
+    class DistanceView: BaseView ,JXSegmentedListContainerViewListDelegate{
+        let label = UILabel()
+        
+        override func setupViews() {
+            addSubviews([label])
+            label.do { v in
+                v.font = .bold(28)
+                v.textAlignment = .center
+                v.textColor = .color(.first)
+                v.text = "3 km"
+                v.yc.addTap(target: self, action: #selector(tapAction))
+            }
+        }
+        override func setupLayout() {
+            label.snp.makeConstraints { make in
+                make.left.right.equalToSuperview()
+                make.top.equalToSuperview().inset(50)
+            }
+        }
+        
+        func listView() -> UIView {
+            return self
+        }
+        @objc func tapAction(){
+            let list = Array(stride(from: 1, to: 100, by: 1)).map { String($0) + " km" }
+            let v = CustomPicker2(title: "距离".l10n, list:list )
+            v.selectedIndex = 0
+            v.show()
+            v.didSelected = {[weak self] (index,value) in
+                guard let self = self else { return  }
+                label.text = value
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/DurationView.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/DurationView.swift
new file mode 100644 (file)
index 0000000..aa5b92d
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  DurationView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import JXSegmentedView
+
+extension SportTargetVC{
+    class DurationView: BaseView ,JXSegmentedListContainerViewListDelegate{
+        let label = UILabel()
+        
+        override func setupViews() {
+            addSubviews([label])
+            label.do { v in
+                v.font = .bold(28)
+                v.textAlignment = .center
+                v.textColor = .color(.first)
+                v.text = "00:30"
+                v.yc.addTap(target: self, action: #selector(tapAction))
+            }
+        }
+        override func setupLayout() {
+            label.snp.makeConstraints { make in
+                make.left.right.equalToSuperview()
+                make.top.equalToSuperview().inset(50)
+            }
+        }
+        
+        func listView() -> UIView {
+            return self
+        }
+        @objc func tapAction(){
+            let v = CustomPicker3(title: "日期".l10n)
+            v.datePicker.datePickerMode = .time
+            v.show()
+            v.didSelected = {[weak self] (date) in
+                guard let self = self else { return  }
+                label.text = date.format(.shortTime)
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Target/SportTargetVC.swift b/ShenYin/ShenYin/UI/Sport/Sport/Target/SportTargetVC.swift
new file mode 100644 (file)
index 0000000..79dcfa0
--- /dev/null
@@ -0,0 +1,127 @@
+//
+//  SportTargetVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/3.
+//
+
+import JXSegmentedView
+
+
+enum SportTargetType{
+    case distance
+    case duration
+    case calorie
+    
+    var name:String{
+        switch self {
+        case .distance:
+            return "距离".l10n
+        case .duration:
+            return "时长".l10n
+        case .calorie:
+            return "卡路里".l10n
+        }
+    }
+    static var all:[SportTargetType] = [.distance,.duration,.calorie]
+}
+
+class SportTargetVC: BaseVC {
+    let okBtn = CustomButton()
+    private let segmentedDataSource = JXSegmentedTitleDataSource()
+    private let segmentedView = JXSegmentedView()
+    private lazy var listContainerView: JXSegmentedListContainerView! = {
+        let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView)
+        return view
+    }()
+    
+    var list:[SportTargetType] = SportTargetType.all
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+
+    }
+    
+    override func setupLayout(){
+        
+        segmentedView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview()
+            m.height.equalTo(44.fitR)
+            m.top.equalToSuperview().inset(kNavBtm)
+        }
+        listContainerView.snp.makeConstraints { (m) in
+            m.width.left.equalToSuperview()
+            m.top.equalTo(segmentedView.snp.bottom)
+            m.bottom.equalToSuperview()
+        }
+        okBtn.snp.makeConstraints { make in
+            make.bottom.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview().inset(51)
+            make.height.equalTo(46)
+        }
+    }
+    override func setupViews(){
+        navBar.title = "设置目标".l10n
+        view.addSubview(segmentedView)
+        view.addSubview(listContainerView)
+        view.addSubview(okBtn)
+        
+        view.backgroundColor = .color(.bgColor)
+        segmentedView.do { (v) in
+            segmentedDataSource.titleNormalColor = .color(.secondary)
+            segmentedDataSource.titleSelectedColor = .color(.first)
+            segmentedDataSource.titleNormalFont = .regular(14)
+            segmentedDataSource.titleSelectedFont = .bold(16)
+            segmentedDataSource.isTitleZoomEnabled = true
+            // segmentedDataSource.titleSelectedZoomScale = 1
+            segmentedDataSource.isTitleStrokeWidthEnabled = true
+            segmentedDataSource.isItemSpacingAverageEnabled = true
+            segmentedDataSource.isTitleColorGradientEnabled = true
+            segmentedDataSource.titles = list.map{"\($0.name)"}
+            segmentedView.backgroundColor = .clear
+            
+            //配置指示器
+            let indicator = JXSegmentedIndicatorLineView()
+            indicator.indicatorWidth = 20
+            indicator.indicatorHeight = 4
+            indicator.verticalOffset = 6
+            indicator.indicatorColor = .color(.sport)
+            segmentedView.indicators = [indicator]
+            
+            v.dataSource = segmentedDataSource
+            v.listContainer = listContainerView
+        }
+        okBtn.set(title: "确定".l10n,style: .sport, target: self, action: #selector(okAction))
+    }
+    @objc func okAction(){
+        self.navigationController?.popViewController(animated: true)
+    }
+}
+
+extension SportTargetVC: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return view
+    }
+}
+
+extension SportTargetVC: JXSegmentedListContainerViewDataSource {
+    func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
+        if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource {
+            return titleDataSource.dataSource.count
+        }
+        return 0
+    }
+    
+    func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
+        let type = list[index]
+        switch type {
+        case .distance:
+            return DistanceView()
+        case .duration:
+            return DurationView()
+        case .calorie:
+            return CaloriView()
+        }
+        
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Tools/IBSpeaker.swift b/ShenYin/ShenYin/UI/Sport/Sport/Tools/IBSpeaker.swift
new file mode 100644 (file)
index 0000000..8175151
--- /dev/null
@@ -0,0 +1,87 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/26.
+//
+
+
+import AVFoundation
+
+class IBSpeaker: NSObject, AVSpeechSynthesizerDelegate {
+    static let shared = IBSpeaker()
+    // MARK: - Properties
+    private let synthesizer = AVSpeechSynthesizer()
+    private var textBuffer: String = "" // 用于存储待播放的文字
+    private var isPlaying: Bool = false // 是否正在播放
+    
+    
+    // MARK: - Initializer
+    override init() {
+        super.init()
+        synthesizer.delegate = self
+    }
+    
+    // MARK: - Public Methods
+    
+    /// 添加文字并开始播放
+    func appendText(_ text: String) {
+        textBuffer += text
+        
+        // 如果没有正在播放,则开始播放
+        if !isPlaying {
+            playNextChunk()
+        }
+    }
+    
+    /// 停止播放并清空缓冲区
+    func stop() {
+        synthesizer.stopSpeaking(at: .immediate)
+        textBuffer = ""
+        isPlaying = false
+    }
+    
+    // MARK: - Private Methods
+    
+    /// 播放缓冲区中的下一段文字
+    private func playNextChunk() {
+        guard !textBuffer.isEmpty else {
+            isPlaying = false
+            return
+        }
+        
+        // 每次播放一段文字(可以根据需要调整)
+        let chunkSize = textBuffer.count // 每次播放的字符数
+        let endIndex = textBuffer.index(textBuffer.startIndex, offsetBy: min(chunkSize, textBuffer.count))
+        let chunk = String(textBuffer[..<endIndex])
+        
+        // 移除已播放的文字
+        textBuffer.removeSubrange(..<endIndex)
+        
+        // 创建语音并播放
+        let utterance = AVSpeechUtterance(string: chunk)
+        utterance.voice = AVSpeechSynthesisVoice(language: "zh-CN") // 设置语言为中文
+        utterance.rate = AVSpeechUtteranceDefaultSpeechRate // 设置语速
+        //        utterance.pitchMultiplier = 1.0 // 设置音调
+        
+        synthesizer.speak(utterance)
+        isPlaying = true
+    }
+    
+    // MARK: - AVSpeechSynthesizerDelegate
+    
+    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
+        print("语音播放完成")
+        // 当前段落播放完成后,继续播放下一段
+        playNextChunk()
+    }
+    
+    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
+        print("语音播放取消")
+        isPlaying = false
+    }
+    
+    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
+        print("正在播放: \(characterRange)")
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Tools/MapManager.swift b/ShenYin/ShenYin/UI/Sport/Sport/Tools/MapManager.swift
new file mode 100644 (file)
index 0000000..b97169e
--- /dev/null
@@ -0,0 +1,58 @@
+//
+//  MapManager.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/21.
+//
+import MAMapKit
+import UIKit
+
+class MapManager {
+    static let shared = MapManager()
+    
+    func setupSdk(){
+        MAMapView.updatePrivacyAgree(AMapPrivacyAgreeStatus.didAgree)
+        AMapServices.shared().apiKey = "57aebc5a63fa3ccb8cb81ae0cab649d1"
+//        //判断是否是首次启动
+//        if !UserDefaults.standard.bool(forKey: "agreeStatus") {
+//            //添加隐私合规弹窗
+//            self.addAlertController()
+//            //更新App是否显示隐私弹窗的状态,隐私弹窗是否包含高德SDK隐私协议内容的状态. since 8.1.0
+//            MAMapView.updatePrivacyShow(AMapPrivacyShowStatus.didShow, privacyInfo: AMapPrivacyInfoStatus.didContain)
+//        }
+    }
+    
+    
+    func addAlertController(){
+        
+        let paragraphStyle : NSMutableParagraphStyle = NSMutableParagraphStyle.init()
+        paragraphStyle.alignment = NSTextAlignment.left
+        
+        let message : NSMutableAttributedString = NSMutableAttributedString.init(string: "\n亲,感谢您对XXX一直以来的信任!我们依据最新的监管要求更新了XXX《隐私权政策》,特向您说明如下\n1.为向您提供交易相关基本功能,我们会收集、使用必要的信息;\n2.基于您的明示授权,我们可能会获取您的位置(为您提供附近的商品、店铺及优惠资讯等)等信息,您有权拒绝或取消授权;\n3.我们会采取业界先进的安全措施保护您的信息安全;\n4.未经您同意,我们不会从第三方处获取、共享或向提供您的信息;", attributes: [NSAttributedString.Key.paragraphStyle:paragraphStyle])
+        
+        message.setAttributes([NSAttributedString.Key.foregroundColor:UIColor.blue], range: message.mutableString.range(of: "《隐私权政策》"))
+        
+        let alert : UIAlertController = UIAlertController.init(title: "温馨提示(隐私合规示例)", message: "", preferredStyle: UIAlertController.Style.alert)
+        
+        alert.setValue(message, forKey: "attributedMessage")
+        
+        let conform : UIAlertAction = UIAlertAction.init(title: "同意", style: UIAlertAction.Style.default) { UIAlertAction in
+            UserDefaults.standard.set(true, forKey: "agreeStatus")
+            UserDefaults.standard.synchronize()
+            //更新用户授权高德SDK隐私协议状态. since 8.1.0
+            MAMapView.updatePrivacyAgree(AMapPrivacyAgreeStatus.didAgree)
+        }
+        
+        let cancel : UIAlertAction = UIAlertAction.init(title: "不同意", style: UIAlertAction.Style.default) { UIAlertAction in
+            UserDefaults.standard.set(false, forKey: "agreeStatus")
+            UserDefaults.standard.synchronize()
+            //更新用户授权高德SDK隐私协议状态. since 8.1.0
+            MAMapView.updatePrivacyAgree(AMapPrivacyAgreeStatus.notAgree)
+        }
+        
+        alert.addAction(conform)
+        alert.addAction(cancel)
+        
+        App.shared.window?.rootViewController?.present(alert, animated: true, completion: nil)
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Tools/SportCalculateUtil.swift b/ShenYin/ShenYin/UI/Sport/Sport/Tools/SportCalculateUtil.swift
new file mode 100644 (file)
index 0000000..9912456
--- /dev/null
@@ -0,0 +1,168 @@
+import Foundation
+
+class SportCalculateUtil {
+    // 地球半径(米)
+    private static let EARTH_RADIUS: Double = 6378137.0
+    // 保留2位小数
+    private static let df: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.minimumFractionDigits = 2
+        formatter.maximumFractionDigits = 2
+        return formatter
+    }()
+    // 保留1位小数(步幅、海拔)
+    private static let df1: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.minimumFractionDigits = 1
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }()
+    
+
+    
+    // 2. 计算平均时速:总里程(km) ÷ 总时长(h)
+    static func calculateAverageSpeed(totalKm: Double, totalTimeSec: Int) -> Double {
+        guard totalKm > 0 && totalTimeSec > 0 else { return 0.0 }
+        let totalHour = Double(totalTimeSec) / 3600.0 // 秒转小时
+        let avgSpeed = totalKm / totalHour
+        return Double((df1.string(from: NSNumber(value: avgSpeed)) ?? "0.0")) ?? 0.0
+    }
+    
+    // ====================== 1. 总里程(同之前,改用TrackPoint) ======================
+    static func calculateTotalDistanceMeter(pointList: [TrackPoint]?) -> Double {
+        guard let pointList = pointList, pointList.count >= 2 else { return 0.0 }
+        var total: Double = 0
+        for i in 0..<pointList.count - 1 {
+            let p1 = pointList[i]
+            let p2 = pointList[i + 1]
+            total += calculateDistanceBetweenTwoPoints(lat1: p1.latitude, lng1: p1.longitude, lat2: p2.latitude, lng2: p2.longitude)
+        }
+        return total // 单位:米
+    }
+    
+    /**
+     * 无传感器估算步数(核心方法)
+     * @param totalDistanceMeter 总里程(米)
+     * @param stride 平均步幅(米/步)
+     * @return 估算步数(取整)
+     */
+    static func estimateStepCount(totalDistanceMeter: Double, stride: Float) -> Int {
+        guard totalDistanceMeter > 0 && stride > 0 else { return 0 }
+        // 总里程 ÷ 步幅 = 步数(四舍五入取整)
+        return Int(round(totalDistanceMeter / Double(stride)))
+    }
+    
+    static func calculateTotalDistanceKm(pointList: [TrackPoint]?) -> Double {
+        let meter = calculateTotalDistanceMeter(pointList: pointList)
+        return Double((df.string(from: NSNumber(value: meter / 1000)) ?? "0.00")) ?? 0.0
+    }
+    
+    // 两点球面距离(米)
+    private static func calculateDistanceBetweenTwoPoints(lat1: Double, lng1: Double, lat2: Double, lng2: Double) -> Double {
+        let radLat1 = lat1 * Double.pi / 180.0
+        let radLng1 = lng1 * Double.pi / 180.0
+        let radLat2 = lat2 * Double.pi / 180.0
+        let radLng2 = lng2 * Double.pi / 180.0
+        let dLat = radLat2 - radLat1
+        let dLng = radLng2 - radLng1
+        
+        let a = sin(dLat / 2) * sin(dLat / 2) +
+        cos(radLat1) * cos(radLat2) *
+        sin(dLng / 2) * sin(dLng / 2)
+        let c = 2 * atan2(sqrt(a), sqrt(1 - a))
+        return EARTH_RADIUS * c
+    }
+    
+    // ====================== 2. 配速(分/公里) ======================
+    static func calculatePace(totalTimeSec: Int, totalDistanceKm: Double) -> String {
+        guard totalDistanceKm > 0 && totalTimeSec > 0 else { return "--'--''" }
+        let secPerKm = Double(totalTimeSec) / totalDistanceKm
+        let min = Int(secPerKm / 60)
+        let sec = Int(secPerKm.truncatingRemainder(dividingBy: 60))
+        return String(format: "%d'%02d''", min, sec)
+    }
+    
+    static func calculatePaceNum(totalTimeSec: Int, totalDistanceKm: Double) -> Double {
+        guard totalDistanceKm > 0 && totalTimeSec > 0 else { return 0 }
+        return Double(totalTimeSec) / totalDistanceKm
+    }
+    
+    // ====================== 3. 卡路里 ======================
+    static func calculateCalorie(totalDistanceKm: Double, weightKg: Float, sportType: Int) -> Int {
+        guard totalDistanceKm > 0 && weightKg > 0 else { return 0 }
+        let coef: Double
+        switch sportType {
+        case 1: coef = 1.0; break // 跑
+        case 2: coef = 0.8; break // 慢跑
+        default: coef = 0.5; break // 走
+        }
+        return Int(round(Double(weightKg) * totalDistanceKm * coef))
+    }
+    
+    // ====================== 4. 步频(步/分钟) ======================
+    static func calculateCadence(stepCount: Int, totalTimeSec: Int) -> Double {
+        guard stepCount > 0 && totalTimeSec > 0 else { return 0 }
+        let min = Double(totalTimeSec) / 60.0
+        let cadence = Double(stepCount) / min
+        return Double((df1.string(from: NSNumber(value: cadence)) ?? "0.0")) ?? 0.0
+    }
+    
+    // ====================== 5. 步幅(米/步) ======================
+    static func calculateStrideLength(totalDistanceMeter: Double, stepCount: Int) -> Double {
+        guard totalDistanceMeter > 0 && stepCount > 0 else { return 0 }
+        let stride = totalDistanceMeter / Double(stepCount)
+        return Double((df1.string(from: NSNumber(value: stride)) ?? "0.0")) ?? 0.0
+    }
+    
+    // ====================== 6. 海拔变化(累计上升、累计下降、净变化) ======================
+    static func calculateAltitudeChange(pointList: [TrackPoint]?) -> AltitudeResult {
+        let res = AltitudeResult()
+        guard let pointList = pointList,!pointList.isEmpty else { return res }
+        
+        let firstAlt = pointList[0].altitude
+        let lastAlt = pointList[pointList.count - 1].altitude
+        res.netChange = lastAlt - firstAlt
+        
+        var up: Double = 0, down: Double = 0
+        for i in 1..<pointList.count {
+            let prev = pointList[i - 1].altitude
+            let curr = pointList[i].altitude
+            let delta = curr - prev
+            // 过滤微小抖动,减少误差(可调整阈值:0.5~1米)
+            if delta > 0.5 {
+                up += delta
+            } else if delta < -0.5 {
+                down -= delta
+            }
+        }
+        res.totalUp = Double((df1.string(from: NSNumber(value: up)) ?? "0.0")) ?? 0.0
+        res.totalDown = Double((df1.string(from: NSNumber(value: down)) ?? "0.0")) ?? 0.0
+        res.netChange = Double((df1.string(from: NSNumber(value: res.netChange)) ?? "0.0")) ?? 0.0
+        return res
+    }
+    
+
+}
+
+
+
+// 存储轨迹点,包含经纬度 + 海拔
+class TrackPoint {
+    var latitude: Double
+    var longitude: Double
+    var altitude: Double // 海拔(米)
+    
+    init(latitude: Double, longitude: Double, altitude: Double) {
+        self.latitude = latitude
+        self.longitude = longitude
+        self.altitude = altitude
+    }
+    }
+// 海拔结果实体
+class AltitudeResult {
+    var totalUp: Double = 0.0    // 累计上升(米)
+    var totalDown: Double = 0.0  // 累计下降(米)
+    var netChange: Double = 0.0  // 净变化(米)
+    }
diff --git a/ShenYin/ShenYin/UI/Sport/Sport/Tools/SportManager.swift b/ShenYin/ShenYin/UI/Sport/Sport/Tools/SportManager.swift
new file mode 100644 (file)
index 0000000..ba6a749
--- /dev/null
@@ -0,0 +1,126 @@
+//
+//  SportManager.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/2/10.
+//
+
+class SportManager {
+    static let shared = SportManager()
+    var sportType = SportType.ride
+    
+    var pointList:[TrackPoint] = [TrackPoint]()
+    
+    var timer:Timer?
+    /// 运动时间
+    var duration = 0
+    /// 运动距离
+    var distance = 0.0
+    /// 当前配速
+    var peisu = 0.0
+    /// 步数
+    var bushu = 0
+    
+    private var hasPlayed100M = false
+    private var hasPlayed500M = false
+    private var hasPlayed1000M = false
+    
+    func start(){
+        duration = 0
+        if let t = timer,t.isValid {
+            t.invalidate()
+        }
+        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
+    }
+    func stop(){
+        if let t = timer,t.isValid {
+            t.invalidate()
+        }
+        SportManager.shared.play(text: "运动已结束")
+    }
+    func pause(){
+        if let t = timer,t.isValid {
+            t.invalidate()
+        }
+        SportManager.shared.play(text: "运动已暂停")
+    }
+    func resume(){
+        if let t = timer,t.isValid {
+            t.invalidate()
+        }
+        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
+    }
+    
+    @objc func timerAction(){
+        self.distance = SportCalculateUtil.calculateTotalDistanceMeter(pointList: pointList)
+        if self.distance == 0 {
+            self.peisu = 0
+            self.bushu = 0
+        }
+        else{
+            self.peisu = (Double(duration)/60.0) / (Double(distance) / 1000.0)
+            self.bushu = Int(distance/0.6)
+        }
+
+        duration += 1
+        NotificationManager.post(type: .updateLocation, objtect: nil)
+        
+        if self.distance > 100.0 && hasPlayed100M == false{
+            hasPlayed100M = true
+            SportManager.shared.play(text: "您已经跑了100米,继续加油")
+        }
+        else if self.distance > 500.0 && hasPlayed500M == false{
+            hasPlayed500M = true
+            SportManager.shared.play(text: "您已经跑了500米,继续加油")
+        }
+        else if self.distance > 1000.0 && hasPlayed1000M == false {
+            hasPlayed1000M = true
+            SportManager.shared.play(text: "您已经跑了1000米,继续加油")
+        }
+    }
+    
+    var durationStr:String{
+        return YLToos.formatSeconds(duration)
+    }
+    var distanceStr:String {
+        return String(format: "%.2f", distance/1000.0)
+    }
+    var peisuStr:String {
+        let minutes = Int(peisu)
+        let seconds = Int(peisu.truncatingRemainder(dividingBy: 1)*60)
+        return String(format: "%d'%02d''", minutes, seconds)
+    }
+    var calorieStr:String {
+        let value = SportCalculateUtil.calculateCalorie(totalDistanceKm: distance, weightKg: 50, sportType: 1)
+        return "\(value)"
+    }
+    var bushuStr:String {
+        return "\(bushu)"
+    }
+    var speedStr:String {
+        let value = distance/Double(duration)
+        return String(format: "%.1f", value)
+    }
+    var avgBuFuStr:String {
+        let value = distance/Double(bushu)
+        return String(format: "%.1f", value)
+    }
+    var avgBuPinStr:String {
+        let bupin = Double(bushu)/(Double(duration)/60.0)
+        return "\(Int(bupin))"
+    }
+    var upStr:String {
+        let value = SportCalculateUtil.calculateAltitudeChange(pointList: pointList)
+        return String(format: "%.1f", value.totalUp)
+    }
+    var downStr:String {
+        let value = SportCalculateUtil.calculateAltitudeChange(pointList: pointList)
+        return String(format: "%.1f", value.totalDown)
+    }
+}
+extension SportManager{
+    func play(text:String ){
+        IBSpeaker.shared.appendText(text)
+    }
+}
+
diff --git a/ShenYin/ShenYin/UI/Sport/SportHomeVC.swift b/ShenYin/ShenYin/UI/Sport/SportHomeVC.swift
new file mode 100644 (file)
index 0000000..cedefa5
--- /dev/null
@@ -0,0 +1,131 @@
+//
+//  SportHomeVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/20.
+//
+
+import JXSegmentedView
+
+
+enum SportHomeType{
+    case sport
+    case record
+    case translate
+    case music
+    
+    var name:String{
+        switch self {
+        case .sport:
+            return "运动"
+        case .record:
+            return "录音"
+        case .translate:
+            return "翻译"
+        case .music:
+            return "音乐"
+        }
+    }
+    static var all:[SportHomeType] = [.sport,.record,.translate,.music]
+}
+
+class SportHomeVC: BaseVC {
+    let icon = UIImageView()
+    private let segmentedDataSource = JXSegmentedTitleDataSource()
+    private let segmentedView = JXSegmentedView()
+    private lazy var listContainerView: JXSegmentedListContainerView! = {
+        let view = JXSegmentedListContainerView(dataSource: self, type: .scrollView)
+        return view
+    }()
+    
+    var list:[SportHomeType] = SportHomeType.all
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        //        AudioPlayView.shared.show()
+        self.view.addSubview(AudioPlayView.shared)
+        MapManager.shared.setupSdk()
+    }
+    
+    override func setupLayout(){
+        icon.snp.makeConstraints { make in
+            make.size.equalTo(32)
+            make.bottom.equalToSuperview().inset(10)
+            make.centerX.equalToSuperview()
+        }
+        segmentedView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview()
+            m.height.equalTo(44.fitR)
+            m.top.equalToSuperview().inset(kNavBtm)
+        }
+        listContainerView.snp.makeConstraints { (m) in
+            m.width.left.equalToSuperview()
+            m.top.equalTo(segmentedView.snp.bottom)
+            m.bottom.equalToSuperview()
+        }
+    }
+    override func setupViews(){
+        icon.do { v in
+            v.image = UIImage(named: "sport_ai")
+        }
+        navBar.addSubview(icon)
+        view.addSubview(segmentedView)
+        view.addSubview(listContainerView)
+        
+        view.backgroundColor = .color(.bgColor)
+        segmentedView.do { (v) in
+            segmentedDataSource.titleNormalColor = .color(.secondary)
+            segmentedDataSource.titleSelectedColor = .color(.first)
+            segmentedDataSource.titleNormalFont = .regular(14)
+            segmentedDataSource.titleSelectedFont = .bold(16)
+            segmentedDataSource.isTitleZoomEnabled = true
+            // segmentedDataSource.titleSelectedZoomScale = 1
+            segmentedDataSource.isTitleStrokeWidthEnabled = true
+            segmentedDataSource.isItemSpacingAverageEnabled = true
+            segmentedDataSource.isTitleColorGradientEnabled = true
+            segmentedDataSource.titles = list.map{"\($0.name)"}
+            segmentedView.backgroundColor = .clear
+            
+            //配置指示器
+            let indicator = JXSegmentedIndicatorLineView()
+            indicator.indicatorWidth = 20
+            indicator.indicatorHeight = 4
+            indicator.verticalOffset = 6
+            indicator.indicatorColor = .color(.theme)
+            segmentedView.indicators = [indicator]
+            
+            v.dataSource = segmentedDataSource
+            v.listContainer = listContainerView
+        }
+    }
+}
+
+extension SportHomeVC: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return view
+    }
+}
+
+extension SportHomeVC: JXSegmentedListContainerViewDataSource {
+    func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
+        if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource {
+            return titleDataSource.dataSource.count
+        }
+        return 0
+    }
+    
+    func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
+        let type = list[index]
+        switch type {
+        case .sport:
+            return SportRootView()
+        case .record:
+            return RecordRootView()
+        case .translate:
+            return TranslateRootView()
+        case .music:
+            return MusicRootView()
+        }
+
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Sport/Translate/TranslateRootView.swift b/ShenYin/ShenYin/UI/Sport/Translate/TranslateRootView.swift
new file mode 100644 (file)
index 0000000..f321243
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  TranslateVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2026/1/20.
+//
+
+import UIKit
+import JXSegmentedView
+
+class TranslateRootView: BaseView{
+    let content = SprotRocrodView()
+    
+    override func setupViews() {
+        addSubviews([content])
+        content.do { v in
+            v.header.titleLabel.text = "翻译".l10n
+            v.item1.titleLabel.text = "对话翻译".l10n
+            v.item1.icon.image = UIImage(named: "sport_record")
+            v.item2.titleLabel.text = "同传听译".l10n
+            v.item2.icon.image = UIImage(named: "sport_call")
+            v.header.yc.addTap(target: self, action: #selector(historyAction))
+            v.item1.yc.addTap(target: self, action: #selector(dialogTranslateAction))
+            v.item2.yc.addTap(target: self, action: #selector(tctyAction))
+        }
+    }
+    
+    override func setupLayout() {
+        content.snp.makeConstraints { make in
+            make.left.right.top.equalToSuperview().inset(18)
+            make.height.equalTo(209)
+        }
+    }
+    @objc func historyAction(){
+        App.shared.goToPage(HistoryRecordVC())
+    }
+    @objc func dialogTranslateAction(){
+        CCPage.gotoDialogTranslate()
+    }
+    @objc func tctyAction(){
+        CCPage.gotoTcty()
+    }
+}
+
+extension TranslateRootView: JXSegmentedListContainerViewListDelegate {
+    func listView() -> UIView {
+        return self
+    }
+}
index 21b1732..7596e8d 100644 (file)
@@ -11,6 +11,7 @@ enum NotificationType:String{
     case deviceValidChange
     case login
     case logout
+    case updateLocation
     
     var value:String{
         switch self {
@@ -20,6 +21,8 @@ enum NotificationType:String{
             return "login"
         case .logout:
             return "logout"
+        case .updateLocation:
+            return "updateLocation"
         default:
             return "unknown"
         }
@@ -36,9 +39,9 @@ class NotificationManager {
         NotificationCenter.default.post(name: Notification.Name(type.value), object: objtect, userInfo:nil)
     }
 
-    @objc func handleNotification(_ notification: Notification) {
-        if let data = notification.userInfo?["data"] as? String {
-            print("Received data: \(data)")
-        }
-    }
+//    @objc func handleNotification(_ notification: Notification) {
+//        if let data = notification.userInfo?["data"] as? String {
+//            print("Received data: \(data)")
+//        }
+//    }
 }
index 72904a7..d068380 100644 (file)
@@ -23,7 +23,7 @@ target 'ShenYin' do
   pod 'SwiftDate'#时间转换
   pod 'SVProgressHUD'#蒙版
   pod 'MBProgressHUD'#蒙版
-  pod 'Kingfisher', '~> 6.0'#图片下载K
+  pod 'Kingfisher'#图片下载
    pod 'FSPagerView'
    pod "TabPageViewController" #tabpage
    pod 'JXSegmentedView'
@@ -51,9 +51,9 @@ target 'ShenYin' do
   pod 'HorizonCalendar'#日历
 
   #高德地图
-#  pod 'AMap3DMap-NO-IDFA', '10.0.700'
-#  pod 'AMapLocation-NO-IDFA'
-#  pod 'AMapSearch-NO-IDFA', '9.7.0'
+  pod 'AMap3DMap-NO-IDFA', '10.0.700'
+  pod 'AMapLocation-NO-IDFA'
+  pod 'AMapSearch-NO-IDFA', '9.7.0'
   
 end