完成意见反馈功能开发
authorliangzhenying <867174124@qq.com>
Fri, 17 Oct 2025 09:58:04 +0000 (17:58 +0800)
committerliangzhenying <867174124@qq.com>
Fri, 17 Oct 2025 09:58:04 +0000 (17:58 +0800)
48 files changed:
ShenYin/ShenYin.xcodeproj/project.pbxproj
ShenYin/ShenYin/Api/HttpClient2.swift
ShenYin/ShenYin/Api/LanConfig.swift [deleted file]
ShenYin/ShenYin/Api/ResponseBaseModel.swift
ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/删除@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/删除@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/反馈历史_1@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/反馈历史_1@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Tools/LogManager.swift
ShenYin/ShenYin/UI/Launch/AppManager.swift
ShenYin/ShenYin/UI/Launch/SceneDelegate.swift
ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackCommitVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackContactView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackImageView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackInputView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackTypeView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/FeedbackBottomView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/FeedbackContentView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/FeedbackHistoryVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/FeedbackQaListView.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/FeedbackVM.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/FeekbackVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Feedback/QaModel.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Instructions/InstructionsDetailVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Instructions/InstructionsModel.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/Instructions/InstructionsVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Mine/MoreSetVC.swift
ShenYin/ShenYin/UI/Mine/Root/MineCell.swift
ShenYin/ShenYin/UI/Mine/Root/MineVC.swift
ShenYin/ShenYin/UI/Mine/UserInfo/CustomCell1.swift
ShenYin/ShenYin/UI/Mode/ModeControView.swift
ShenYin/ShenYin/UI/Sleep/MusicModel.swift
ShenYin/ShenYin/UI/common/ImagePickerManager.swift
ShenYin/ShenYin/UI/common/PlaceHolderTextView.swift

index aeed467..298fe4c 100644 (file)
                                );
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = ShenYin/Info.plist;
-                               INFOPLIST_KEY_CFBundleDisplayName = "深音";
+                               INFOPLIST_KEY_CFBundleDisplayName = DeepChords;
                                INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描附近蓝牙设备需要获得权限";
                                INFOPLIST_KEY_NSCameraUsageDescription = "修改用户头像、提交意见反馈需要访问相册使用摄像头";
                                INFOPLIST_KEY_NSMicrophoneUsageDescription = "录音需要使用麦克风";
                                );
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = ShenYin/Info.plist;
-                               INFOPLIST_KEY_CFBundleDisplayName = "深音";
+                               INFOPLIST_KEY_CFBundleDisplayName = DeepChords;
                                INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描附近蓝牙设备需要获得权限";
                                INFOPLIST_KEY_NSCameraUsageDescription = "修改用户头像、提交意见反馈需要访问相册使用摄像头";
                                INFOPLIST_KEY_NSMicrophoneUsageDescription = "录音需要使用麦克风";
index b1d45e5..4bf340c 100644 (file)
@@ -55,6 +55,18 @@ enum CommomApi
     case musicUrl(filedId:String)
     /// 上传图片
     case uploadImage(param:[String:Any])
+    /// 获取产品列表
+    case getProductList
+    /// 获取产品说明书
+    case productBook(productId:String)
+    /// 获取qa分类
+    case qaCategory
+    /// 获取qa列表
+    case qaList(categoryId:String,page:Int)
+    /// 提交意见反馈
+    case commitFeedback(param:[String:Any])
+    /// 获取提交的意见反馈
+    case feedbackHistory
 }
 
 // 实现TargetType协议
@@ -121,12 +133,27 @@ extension CommomApi{
             return "/res/music-file/path/\(id)"
         case .uploadImage(_):
             return "/res/image"
+        case .getProductList:
+            return "/dev/product/list"
+        case .productBook(let productId):
+            return "/res/prod-book-file?productId=\(productId)"
+        case .qaCategory:
+            return "/uc/qa-category/list"
+        case .qaList(let categoryId,let page):
+            return "/uc/qa/list/\(page)?categoryId=\(categoryId)"
+            
+        case .commitFeedback(_):
+            return "/uc/feedback"
+        case .feedbackHistory:
+            return "/uc/feedback"
+            
+            
         }
     }
     
     var method:RequestMethod{
         switch self {
-        case .register(_),.phoneCodeLogin(_),.emailCodeLogin(_),.login(_):
+        case .register(_),.phoneCodeLogin(_),.emailCodeLogin(_),.login(_),.commitFeedback(_):
             return .post
         case .updatePwd(_),.updateUserInfo(_),.updatePhone(_),.updateEmail(_):
             return .put
@@ -175,6 +202,9 @@ extension CommomApi{
         case let .uploadImage(para):
             params += para
             break
+        case let .commitFeedback(para):
+            params += para
+            break
         default:
             break
         }
@@ -246,33 +276,71 @@ class HttpClient{
                 Logger.debug("下载进度:\(progress)") // 进度值在 0 到 1 之间
             }
             task.downloadHandler = { response, tempUrl in
-                if let destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(fileId).mp3") {
-                    if FileManager.default.fileExists(atPath: destinationUrl.path) {
-                        Logger.debug("文件已经存在,先移除 => ",url)
+                DispatchQueue.main.sync {
+                    if let destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(fileId).mp3") {
+                        if FileManager.default.fileExists(atPath: destinationUrl.path) {
+                            Logger.debug("文件已经存在,先移除 => ",url)
+                            do {
+                                try FileManager.default.removeItem(atPath: destinationUrl.path)
+                            }catch{
+                                Logger.debug("移除文件失败:\(error)")
+                                completionHandler(nil)
+                            }
+                        }
                         do {
-                            try FileManager.default.removeItem(atPath: destinationUrl.path)
-                        }catch{
-                            Logger.debug("移除文件失败:\(error)")
+                            try FileManager.default.moveItem(at: tempUrl, to: destinationUrl)
+                            Logger.debug("文件下载成功,保存路径:\(destinationUrl.path)")
+                            completionHandler(destinationUrl.path)
+                        } catch {
+                            Logger.debug("移动文件失败:\(error)")
                             completionHandler(nil)
                         }
                     }
-                    do {
-                        try FileManager.default.moveItem(at: tempUrl, to: destinationUrl)
-                        Logger.debug("文件下载成功,保存路径:\(destinationUrl.path)")
-                        completionHandler(destinationUrl.path)
-                    } catch {
-                        Logger.debug("移动文件失败:\(error)")
+                    else{
                         completionHandler(nil)
                     }
                 }
-                else{
-                    completionHandler(nil)
+            }
+            task.run()
+        }
+    }
+    static func download(url:String,completionHandler:@escaping ((String?) -> Void)){
+        Logger.debug("dowload => ",url)
+        if let request = URLRequest(urlString:url) {
+            let task = HTTP(request, isDownload: true)
+            task.progress = { progress in
+                Logger.debug("下载进度:\(progress)") // 进度值在 0 到 1 之间
+            }
+            task.downloadHandler = { response, tempUrl in
+                DispatchQueue.main.sync {
+                    if let destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(Date().timeIntervalSince1970).pdf") {
+                        Logger.debug("保存路径 => ",destinationUrl)
+                        if FileManager.default.fileExists(atPath: destinationUrl.path) {
+                            Logger.debug("文件已经存在,先移除 => ",url)
+                            do {
+                                try FileManager.default.removeItem(atPath: destinationUrl.path)
+                            }catch{
+                                Logger.debug("移除文件失败:\(error)")
+                                completionHandler(nil)
+                            }
+                        }
+                        do {
+                            try FileManager.default.moveItem(at: tempUrl, to: destinationUrl)
+                            Logger.debug("文件下载成功,保存路径:\(destinationUrl.path)")
+                            completionHandler(destinationUrl.path)
+                        } catch {
+                            Logger.debug("移动文件失败:\(error)")
+                            completionHandler(nil)
+                        }
+                    }
+                    else{
+                        completionHandler(nil)
+                    }
                 }
             }
             task.run()
         }
     }
-    
 }
 
 extension HttpClient{
diff --git a/ShenYin/ShenYin/Api/LanConfig.swift b/ShenYin/ShenYin/Api/LanConfig.swift
deleted file mode 100644 (file)
index 904b077..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-//
-//  LangConfig.swift
-//  ShenYin
-//
-//  Created by liangshi on 2025/10/15.
-//
-
-enum AppSupportLanguage{
-    case chinse
-    case english
-    case japanese
-    case korean
-    case french
-    case spanish
-    case portuguese
-    case indonesian
-    case russian
-    case malay
-    
-    static var all:[AppSupportLanguage]{
-        return [.chinse,.english,.japanese,.korean,.french,.spanish,.portuguese,.indonesian,.russian,.malay]
-    }
-    var title:String{
-        switch self {
-        case .chinse:
-            return "中文"
-        case .english:
-            return "英文"
-        case .japanese:
-            return "日文"
-        case .korean:
-            return "韩文"
-        case .french:
-            return "法语"
-        case .spanish:
-            return "西班牙文"
-        case .portuguese:
-            return "葡萄牙文"
-        case .indonesian:
-            return "印度尼西亚文"
-        case .russian:
-            return "俄文"
-        case .malay:
-            return "马来文"
-        }
-    }
-    var map:[String:String]{
-        switch self {
-        case .chinse:
-            return englishMap
-        case .english:
-            return englishMap
-        case .japanese:
-            return japaneseMap
-        case .korean:
-            return koreanMap
-        case .french:
-            return frenchMap
-        case .spanish:
-            return spanishMap
-        case .portuguese:
-            return portugueseMap
-        case .indonesian:
-            return indonesianMap
-        case .russian:
-            return russianMap
-        case .malay:
-            return malayMap
-        }
-    }
-}
-
-let chineseMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let englishMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let japaneseMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let koreanMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let frenchMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let spanishMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let portugueseMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let indonesianMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let russianMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-let malayMap:[String:String] = [
-    "登录" : "login",
-    "注册" : "register",
-]
-
-extension String{
-    var l10n:String{
-        if let value = Config.appLanguage.map[self] {
-            return value
-        }
-        else{
-            return self
-        }
-    }
-}
-
-
index 8f98274..74c8ebd 100644 (file)
@@ -16,3 +16,8 @@ public class ResponseBaseModel: HandyJSON {
 
     public var isSuccess: Bool { return success}
 }
+
+
+class CommonDataModel:ResponseBaseModel {
+    var data = ""
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Contents.json
new file mode 100644 (file)
index 0000000..c8ff4ea
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame_2291@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame_2291@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@2x.png b/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@2x.png
new file mode 100644 (file)
index 0000000..b4befe4
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@3x.png b/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@3x.png
new file mode 100644 (file)
index 0000000..de783a7
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/add_picture.imageset/Frame_2291@3x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/Contents.json
new file mode 100644 (file)
index 0000000..ce54822
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "删除@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "删除@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/删除@2x.png b/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/删除@2x.png
new file mode 100644 (file)
index 0000000..5957d97
Binary files /dev/null and "b/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/\345\210\240\351\231\244@2x.png" differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/删除@3x.png b/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/删除@3x.png
new file mode 100644 (file)
index 0000000..f691e28
Binary files /dev/null and "b/ShenYin/ShenYin/Assets.xcassets/mine/delete_icon.imageset/\345\210\240\351\231\244@3x.png" differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Contents.json
new file mode 100644 (file)
index 0000000..39978db
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame_2252@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame_2252@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@2x.png b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@2x.png
new file mode 100644 (file)
index 0000000..263e0bf
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@3x.png b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@3x.png
new file mode 100644 (file)
index 0000000..26d63da
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_chat.imageset/Frame_2252@3x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Contents.json
new file mode 100644 (file)
index 0000000..5c4d3b1
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@2x.png b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@2x.png
new file mode 100644 (file)
index 0000000..377c4d7
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@3x.png b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@3x.png
new file mode 100644 (file)
index 0000000..8bc7f28
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_edit.imageset/Frame@3x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/Contents.json
new file mode 100644 (file)
index 0000000..8e19255
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "反馈历史_1@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "反馈历史_1@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/反馈历史_1@2x.png b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/反馈历史_1@2x.png
new file mode 100644 (file)
index 0000000..9c915a7
Binary files /dev/null and "b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/\345\217\215\351\246\210\345\216\206\345\217\262_1@2x.png" differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/反馈历史_1@3x.png b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/反馈历史_1@3x.png
new file mode 100644 (file)
index 0000000..b818b40
Binary files /dev/null and "b/ShenYin/ShenYin/Assets.xcassets/mine/feedback_history.imageset/\345\217\215\351\246\210\345\216\206\345\217\262_1@3x.png" differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Contents.json
new file mode 100644 (file)
index 0000000..5c4d3b1
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "Frame@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "Frame@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@2x.png b/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@2x.png
new file mode 100644 (file)
index 0000000..a3bf533
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@2x.png differ
diff --git a/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@3x.png b/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@3x.png
new file mode 100644 (file)
index 0000000..ed2e1c3
Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/mine/mine_cell_lan.imageset/Frame@3x.png differ
index 5ae693a..173baf7 100644 (file)
@@ -25,7 +25,7 @@ class Logger {
     
     
     private func getPath()->String{
-        let foldPath = NSHomeDirectory() + "/Documents"
+        let foldPath = NSHomeDirectory() + "/Documents/"
         let fileManager = FileManager.default
         if !fileManager.fileExists(atPath: foldPath) {
             do {
index afd8487..dfaa579 100644 (file)
@@ -48,6 +48,13 @@ class App{
             topVC().navigationController?.popToViewController(vcs[index], animated: true)
         }
     }
+    func popToRootPage(){
+        topVC().navigationController?.popToRootViewController(animated: true)
+    }
+    var tabController:UITabBarController?{
+        let tb = window?.rootViewController as? UITabBarController
+        return tb
+    }
 }
 extension App{
     private func getTopViewController(from vc: UIViewController) -> UIViewController {
@@ -65,8 +72,8 @@ extension App{
 
 extension App{
     var deviceValid:Bool{
-        return BleManager.shared.isConnetd
-//        return !BleManager.shared.isConnetd
+//        return BleManager.shared.isConnetd
+        return !BleManager.shared.isConnetd
     }
 }
 
index f6032c7..4dbefb7 100644 (file)
@@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
         
         App.shared.window = window
         window?.rootViewController = BaseTC()
-//        window?.rootViewController = UINavigationController(rootViewController: AudioPlayerVC())
+//        window?.rootViewController = UINavigationController(rootViewController: FeedbackCommitVC())
         AppLaunch.start()
     }
 
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackCommitVC.swift b/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackCommitVC.swift
new file mode 100644 (file)
index 0000000..bc73177
--- /dev/null
@@ -0,0 +1,170 @@
+//
+//  FeedbackCommitVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+import UIKit
+
+class FeedbackCommitVC:BaseVC{
+    let bgView = UIScrollView()
+    let typeView = FeedbackTypeView()
+    let contentView = FeedbackInputView()
+    let imgView = FeedbackImageView()
+    let contactView = FeedbackContactView()
+    let loginBtn = CustomButton()
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        
+    }
+    override func navRightViewTapped() {
+        App.shared.goToPage(FeedbackHistoryVC())
+    }
+    override func setupViews() {
+        self.navBar.title = "意见反馈"
+        navBar.rightView.setImage(UIImage(named: "feedback_history"), for: .normal)
+
+        view.addSubview(bgView)
+        bgView.addSubviews([typeView,contentView,imgView,contactView,loginBtn])
+        
+        bgView.do { v in
+            v.backgroundColor = .white
+            v.yc.setCornerRadius(12)
+        }
+        loginBtn.set(title: "确定",style: .theme1, target: self, action: #selector(loginAction))
+    }
+    override func setupLayout() {
+        bgView.snp.makeConstraints { make in
+            make.top.equalTo(kNavBtm + 10)
+            make.width.equalTo(kScreenWid - 36)
+            make.height.equalTo(kScreenHei - kNavBtm - 20)
+            make.left.equalToSuperview().inset(18)
+        }
+        typeView.snp.makeConstraints { make in
+            make.top.equalToSuperview()
+            make.left.equalToSuperview()
+            make.width.equalToSuperview()
+            make.height.equalTo(80)
+        }
+        contentView.snp.makeConstraints { make in
+            make.top.equalTo(typeView.snp.bottom).offset(15)
+            make.left.equalToSuperview()
+            make.width.equalToSuperview()
+            make.height.equalTo(150)
+        }
+        imgView.snp.makeConstraints { make in
+            make.top.equalTo(contentView.snp.bottom).offset(15)
+            make.left.equalToSuperview()
+            make.width.equalToSuperview()
+            make.height.equalTo(120)
+        }
+        contactView.snp.makeConstraints { make in
+            make.top.equalTo(imgView.snp.bottom).offset(15)
+            make.left.equalToSuperview()
+            make.width.equalToSuperview()
+            make.height.equalTo(90)
+        }
+        loginBtn.snp.makeConstraints { make in
+            make.top.equalTo(contactView.snp.bottom).offset(40)
+            make.centerX.equalToSuperview()
+            make.width.equalTo(kScreenWid - 102)
+            make.height.equalTo(55)
+            make.bottom.equalToSuperview().inset(50)
+        }
+    }
+    @objc func loginAction(){
+        let content = contentView.textView.textView.text ?? ""
+        if content.isEmpty && imgView.dataArray.count == 1 {
+            YLprogressHUD.showAutoHiden("内容不能为空")
+            return
+        }
+        
+        
+        if imgView.dataArray.count > 1 {
+            YLprogressHUD.showLoading()
+            uploadImages { [weak self] urls in
+                guard let self = self else{return}
+                if let list = urls {
+                    YLprogressHUD.hidden()
+                    startCommit(list)
+                }
+                else{
+                    YLprogressHUD.showAutoHiden("请求失败")
+                }
+            }
+        }
+        else{
+            startCommit([])
+        }
+    }
+    func uploadImages(_ finished:@escaping (([String]?) -> Void)){
+        var urls = [String]()
+        let dispatchGroup = DispatchGroup()
+        for item in imgView.dataArray {
+            if !item.isAdd {
+                dispatchGroup.enter()
+                startUploadImage(item.img) { url in
+                    if let url = url {
+                        urls.append(url)
+                    }
+                    dispatchGroup.leave()
+                }
+            }
+        }
+        dispatchGroup.notify(queue:.main) {
+            if urls.count == self.imgView.dataArray.count - 1{
+                finished(urls)
+            }
+            else{
+                finished(nil)
+            }
+        }
+    }
+    
+    func startUploadImage(_ img:UIImage,finished:@escaping ((String?) -> Void)){
+        guard let path = CacheManager.saveImg(img) else{
+            finished(nil)
+            return
+        }
+        let url = URL(fileURLWithPath: path)
+        
+        var params:[String:Any] = [:]
+        params["type"] = "report"
+        params["fileUrl"] = url
+        let api =  CommomApi.uploadImage(param: params)
+        HttpClient.uploadImage(api){[weak self] (model:CommonDataModel, res) in
+            if model.isSuccess{
+                finished(model.data)
+            }
+            else{
+                finished(nil)
+            }
+        }
+    }
+    func startCommit(_ imgs:[String]){
+        let phone = contactView.textField.text ?? ""
+        let content = contentView.textView.textView.text ?? ""
+        
+        
+        YLprogressHUD.showLoading()
+        var params:[String:Any] = [:]
+        params["type"] = typeView.type ?? ""
+        params["images"] = imgs
+        params["description"] = content
+        params["phone"] = phone
+        
+        let api =  CommomApi.commitFeedback(param: params)
+        HttpClient.request(api) {[weak self] (model:ResponseBaseModel,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                YLprogressHUD.hidden()
+            }
+            else{
+                YLprogressHUD.showAutoHiden(model.msg)
+            }
+        }
+
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackContactView.swift b/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackContactView.swift
new file mode 100644 (file)
index 0000000..f4854a2
--- /dev/null
@@ -0,0 +1,53 @@
+//
+//  FeedbackContectView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+class FeedbackContactView: BaseView,UITextFieldDelegate {
+    let titleLabel = UILabel()
+    let bgView = UIView()
+    let textField = UITextField()
+    
+    override func setupViews() {
+        addSubviews([titleLabel,bgView])
+        bgView.addSubview(textField)
+        
+        titleLabel.do { v in
+            v.textAlignment = .center
+            v.font = .bold(14)
+            v.textColor = .hexString("#666666")
+            v.text = "联系方式"
+        }
+        bgView.do { v in
+            v.backgroundColor = .hexString("#F7F8F9")
+            v.yc.setCornerRadius(12)
+        }
+        textField.do { v in
+            v.placeholder = "请输入手机号或邮箱"
+            v.font = .regular(14)
+            v.returnKeyType = .done
+            v.delegate = self
+        }
+    }
+    
+    override func setupLayout() {
+        titleLabel.snp.makeConstraints { make in
+            make.top.equalToSuperview()
+            make.left.equalToSuperview().inset(16)
+        }
+        bgView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(16)
+            make.top.equalTo(titleLabel.snp.bottom).offset(10)
+            make.height.equalTo(50)
+        }
+        textField.snp.makeConstraints { make in
+            make.edges.equalToSuperview().inset(15)
+        }
+    }
+    func textFieldShouldReturn(_ textField: UITextField) -> Bool{
+        textField.resignFirstResponder()
+        return false
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackImageView.swift b/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackImageView.swift
new file mode 100644 (file)
index 0000000..30eb2fb
--- /dev/null
@@ -0,0 +1,167 @@
+//
+//  FeedbackImageView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+import Foundation
+import UIKit
+
+class FeedbackImageCellModel{
+    
+    var img = UIImage()
+    var isAdd = false
+    init(img: UIImage = UIImage(), isAdd: Bool = false) {
+        self.img = img
+        self.isAdd = isAdd
+    }
+}
+
+
+final class FeedbackImageView: BaseView {
+    let imgPicker = ImagePickerManager()
+    var dataArray = [FeedbackImageCellModel(img: UIImage(named: "add_picture")!, isAdd: true)]
+    private var collectionView:UICollectionView!
+
+    override func setupViews() {
+        let layout = UICollectionViewFlowLayout()
+        layout.scrollDirection = .horizontal
+        //layout.minimumInteritemSpacing = 10.fitR
+        layout.minimumLineSpacing = 18
+        layout.itemSize = CGSize(width: 90, height: 90)
+        collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
+        addSubview(collectionView)
+        
+        collectionView.do { (v) in
+            v.dataSource = self
+            v.delegate = self
+            v.backgroundColor = .clear
+            v.bounces = true
+            v.contentInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
+            v.register(FeedbackImageCell.self, forCellWithReuseIdentifier: FeedbackImageCell.reuseID)
+        }
+    }
+    override func setupLayout() {
+        collectionView.snp.makeConstraints { (m) in
+            m.top.equalToSuperview()
+            m.left.right.bottom.equalToSuperview()
+        }
+    }
+    func scrollToBottom() {
+        let itemCount = collectionView.numberOfItems(inSection: 0)
+        if itemCount > 0 {
+            let lastIndexPath = IndexPath(item: itemCount - 1, section: 0)
+            collectionView.scrollToItem(at: lastIndexPath, at:.bottom, animated: true)
+        }
+    }
+    @objc func addAction() -> Void {
+        imgPicker.showImagePicker2(from: App.shared.topVC()) {[weak self] image in
+            guard let self = self else { return  }
+            guard let image = image,let img = UIImage.resizeImage(image: image, toWidth: 300) else { return}
+            let model = FeedbackImageCellModel(img:image, isAdd: false)
+            dataArray.insert(model, at: dataArray.count - 1)
+            collectionView.reloadData()
+            scrollToBottom()
+        }
+    }
+    func deleteAction(_ model:FeedbackImageCellModel){
+        dataArray.removeAll { item in
+            return item === model
+        }
+        collectionView.reloadData()
+    }
+
+}
+
+extension FeedbackImageView: UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout  {
+    func numberOfSections(in collectionView: UICollectionView) -> Int {
+        return 1
+    }
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return dataArray.count
+    }
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        let cell = FeedbackImageCell.cell(withCollectionView: collectionView, indexPath: indexPath)
+        cell.model = dataArray[indexPath.row]
+        cell.deleteAction = {[weak self] cell in
+            guard let self = self else {return}
+            deleteAction(cell.model)
+        }
+        return cell
+    }
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        let model = dataArray[indexPath.row]
+        if model.isAdd {
+            addAction()
+        }
+    }
+}
+
+
+
+class FeedbackImageCell: BaseCollectionViewCell {
+    let bgView = UIView()
+    let imgV = UIImageView()
+    let playImgV = UIImageView()
+    var deleteAction:((_ cell:FeedbackImageCell)->Void)?
+
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        contentView.addSubview(bgView)
+        bgView.addSubview(imgV)
+        bgView.addSubview(playImgV)
+        
+
+//        bgView.backgroundColor = .green
+        
+        bgView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        imgV.snp.makeConstraints { (make) in
+            make.left.equalToSuperview()
+            make.top.equalToSuperview().inset(8)
+            make.size.equalTo(80)
+        }
+
+        playImgV.snp.makeConstraints { (make) in
+            make.size.equalTo(32)
+            make.right.equalToSuperview()
+            make.centerY.equalTo(16)
+        }
+
+        
+        imgV.do { v in
+            v.contentMode = .scaleAspectFill
+            v.clipsToBounds = true
+            v.yc.setCornerRadius(12)
+            v.clipsToBounds = true
+        }
+        playImgV.do { v in
+//            v.backgroundColor = .red
+            v.image = UIImage(named: "delete_icon")
+            v.contentMode = .top
+            v.yc.addTap(target: self, action: #selector(tapAction))
+        }
+    }
+    
+    @objc func tapAction(){
+        deleteAction?(self)
+    }
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    var model:FeedbackImageCellModel!{
+        didSet{
+            imgV.image = model.img
+            if model.isAdd {
+                playImgV.isHidden = true
+            }
+            else{
+                playImgV.isHidden = false
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackInputView.swift b/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackInputView.swift
new file mode 100644 (file)
index 0000000..8c0f1f0
--- /dev/null
@@ -0,0 +1,49 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+class FeedbackInputView: BaseView {
+    let titleLabel = UILabel()
+    let bgView = UIView()
+    let textView = PlaceHolderTextView2()
+    
+    override func setupViews() {
+        //        backgroundColor = .red
+        addSubviews([titleLabel,bgView])
+        bgView.addSubview(textView)
+        
+        titleLabel.do { v in
+            v.textAlignment = .center
+            v.font = .bold(14)
+            v.textColor = .hexString("#666666")
+            v.text = "问题描述或建议"
+        }
+        bgView.do { v in
+            v.backgroundColor = .hexString("#F7F8F9")
+            v.yc.setCornerRadius(12)
+        }
+        textView.do {v in
+            v.placeHolde = "请至少用10个字描述问题,并添加截图,这有助于问题快速解决。"
+            v.backgroundColor = .clear
+        }
+    }
+    
+    override func setupLayout() {
+        titleLabel.snp.makeConstraints { make in
+            make.top.equalToSuperview()
+            make.left.equalToSuperview().inset(16)
+        }
+        bgView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(16)
+            make.top.equalTo(titleLabel.snp.bottom).offset(10)
+            make.bottom.equalToSuperview()
+        }
+        textView.snp.makeConstraints({ make in
+            make.edges.equalToSuperview().inset(10)
+        })
+    }
+
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackTypeView.swift b/ShenYin/ShenYin/UI/Mine/Feedback/Commit/FeedbackTypeView.swift
new file mode 100644 (file)
index 0000000..191180f
--- /dev/null
@@ -0,0 +1,106 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+class FeedbackTypeView: BaseView {
+    let titleLabel = UILabel()
+    let errorItem = SelectedTypeItem()
+    let adviceItem = SelectedTypeItem()
+    
+    var isOk:Bool{
+        return errorItem.isSelected || adviceItem.isSelected
+    }
+    var type:String?{
+        if adviceItem.isSelected {
+            return adviceItem.titleLabel.text
+        }
+        if errorItem.isSelected {
+            return adviceItem.titleLabel.text
+        }
+        return nil
+    }
+    
+    override func setupViews() {
+//        backgroundColor = .red
+        addSubviews([titleLabel,errorItem,adviceItem])
+        
+        titleLabel.do { v in
+            v.textAlignment = .center
+            v.font = .bold(14)
+            v.textColor = .hexString("#666666")
+            v.text = "问题类型"
+        }
+        errorItem.do { v in
+            v.titleLabel.text = "功能异常"
+            v.yc.setCornerRadius(17)
+            v.yc.addTap(target: self, action: #selector(errorAction))
+        }
+        adviceItem.do { v in
+            v.titleLabel.text = "意见与反馈"
+            v.yc.setCornerRadius(17)
+            v.yc.addTap(target: self, action: #selector(adviceAction))
+        }
+    }
+    
+    override func setupLayout() {
+        titleLabel.snp.makeConstraints { make in
+            make.left.top.equalToSuperview().inset(16)
+        }
+        errorItem.snp.makeConstraints { make in
+            make.left.equalToSuperview().inset(16)
+            make.top.equalTo(titleLabel.snp.bottom).offset(10)
+            make.width.equalTo(95)
+            make.height.equalTo(34)
+        }
+        adviceItem.snp.makeConstraints { make in
+            make.left.equalTo(errorItem.snp.right).offset(16)
+            make.top.equalTo(titleLabel.snp.bottom).offset(10)
+            make.width.equalTo(95)
+            make.height.equalTo(34)
+        }
+    }
+    
+    @objc func errorAction(){
+        errorItem.isSelected = true
+        adviceItem.isSelected = false
+    }
+    @objc func adviceAction(){
+        errorItem.isSelected = false
+        adviceItem.isSelected = true
+    }
+    class SelectedTypeItem: BaseView {
+        let titleLabel = UILabel()
+        
+        var isSelected:Bool = false{
+            didSet{
+                if isSelected {
+                    titleLabel.textColor = .white
+                    backgroundColor = .color(.theme)
+                }
+                else{
+                    titleLabel.textColor = .hexString("#121212")
+                    backgroundColor = .hexString("#F7F8F9")
+                }
+            }
+        }
+        
+        override func setupViews() {
+            backgroundColor = .hexString("#F7F8F9")
+            addSubviews([titleLabel])
+            titleLabel.do { v in
+                v.textAlignment = .center
+                v.font = .regular(14)
+                v.yc.setCornerRadius(40)
+            }
+        }
+        
+        override func setupLayout() {
+            titleLabel.snp.makeConstraints { make in
+                make.edges.equalToSuperview()
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackBottomView.swift b/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackBottomView.swift
new file mode 100644 (file)
index 0000000..ba1ceff
--- /dev/null
@@ -0,0 +1,88 @@
+//
+//  FeedbackBottomView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+
+
+class FeedbackBottomView: BaseView {
+    let feedbackItem = RecordLanguageItem()
+    let chatItem = RecordLanguageItem()
+    let line = UIView()
+    
+    override func setupViews() {
+        backgroundColor = .white
+        addSubviews([feedbackItem,chatItem,line])
+        feedbackItem.do { v in
+            v.icon.image = UIImage(named: "feedback_edit")
+            v.titleLabel.text = "意见反馈"
+//            v.backgroundColor = .green
+        }
+        line.do { v in
+            v.backgroundColor = .hexString("#999999")
+            
+        }
+        chatItem.do { v in
+            v.icon.image = UIImage(named: "feedback_chat")
+            v.titleLabel.text = "在线咨询"
+//            v.backgroundColor = .red
+        }
+    }
+    override func setupLayout() {
+        feedbackItem.snp.makeConstraints { make in
+//            make.width.equalToSuperview().multipliedBy(0.5)
+            make.height.equalToSuperview()
+            make.top.equalToSuperview()
+            make.centerX.equalTo(kScreenWid/4)
+        }
+        chatItem.snp.makeConstraints { make in
+//            make.width.equalToSuperview().multipliedBy(0.5)
+            make.height.equalToSuperview()
+            make.top.equalToSuperview()
+            make.centerX.equalTo(kScreenWid/4*3)
+
+        }
+        line.snp.makeConstraints { make in
+            make.width.equalTo(1)
+            make.height.equalTo(22)
+            make.centerX.equalToSuperview()
+            make.top.equalToSuperview().inset(12)
+        }
+    }
+    
+    class RecordLanguageItem: BaseView {
+        let icon = UIImageView()
+        let titleLabel = UILabel()
+        
+        var language:AiSupportLanguage = AiSupportLanguage.chinse{
+            didSet{
+                titleLabel.text = language.title
+            }
+        }
+        
+        override func setupViews() {
+            addSubviews([titleLabel,icon])
+            icon.do { v in
+            }
+            titleLabel.do { v in
+                v.textColor = .rgb(0x121212)
+                v.font = .bold(16)
+            }
+        }
+        
+        override func setupLayout() {
+            icon.snp.makeConstraints { make in
+                make.size.equalTo(20)
+                make.top.equalToSuperview().inset(13)
+                make.left.equalToSuperview().inset(30)
+            }
+            titleLabel.snp.makeConstraints { make in
+                make.left.equalTo(icon.snp.right).offset(1)
+                make.centerY.equalTo(icon)
+                make.right.equalToSuperview().inset(30)
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackContentView.swift b/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackContentView.swift
new file mode 100644 (file)
index 0000000..0f99200
--- /dev/null
@@ -0,0 +1,133 @@
+//
+//  FeedbackContentView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+class FeedbackContentModel{
+    var content = ""
+    var isSend = false
+    init(content: String = "", isSend: Bool = false) {
+        self.content = content
+        self.isSend = isSend
+    }
+}
+
+class FeedbackContentView: BaseView {
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list = [FeedbackContentModel]()
+    
+    override func setupViews() {
+        backgroundColor = .clear
+        tableView.do { v in
+            v.backgroundColor = .clear
+            v.delegate = self
+            v.dataSource = self
+            v.separatorStyle = .none
+            v.estimatedRowHeight = 50
+            v.register(FeedbackContentViewCell.self, forCellReuseIdentifier: FeedbackContentViewCell.reuseID)
+        }
+        
+        addSubview(tableView)
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(0)
+            make.top.bottom.equalToSuperview().inset(0)
+        }
+    }
+    
+    func addData(model:FeedbackContentModel){
+        list.append(model)
+        tableView.reloadData()
+        if list.count > 0 {
+            tableView.scrollToRow(at: IndexPath(row: list.count - 1, section: 0), at: .none, animated: true)
+        }
+    }
+}
+extension FeedbackContentView:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return list.count
+    }
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = FeedbackContentViewCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.data = list[indexPath.row]
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let data = list[indexPath.row]
+        
+    }
+}
+
+class FeedbackContentViewCell:BaseTableViewCell{
+    let bgView = UIView()
+    let originalLabel = UILabel()
+    
+    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.backgroundColor = .rgb(0xF6F6F6)
+        
+        bgView.do { v in
+            v.yc.setCornerRadius(12)
+        }
+        originalLabel.do { v in
+            v.font = .bold(14)
+            v.textColor = .hexString("#606D80")
+            v.numberOfLines = 0
+            v.textAlignment = .left
+        }
+        
+        contentView.addSubview(bgView)
+        bgView.addSubviews([originalLabel])
+    }
+    
+    override func setupLayout() {
+        bgView.snp.makeConstraints { make in
+            make.top.bottom.equalToSuperview().inset(10)
+            make.width.lessThanOrEqualTo(kScreenWid - 36)
+            make.left.equalToSuperview().inset(18)
+        }
+        
+        originalLabel.snp.makeConstraints { make in
+            make.top.bottom.equalToSuperview().inset(14)
+            make.left.right.equalToSuperview().inset(12)
+            make.width.lessThanOrEqualTo(kScreenWid - 60)
+            make.width.greaterThanOrEqualTo(60)
+            make.height.greaterThanOrEqualTo(19)
+        }
+    }
+    
+    var data:FeedbackContentModel!{
+        didSet{
+            originalLabel.text = data.content
+            if data.isSend {
+                bgView.backgroundColor = .color(.theme)
+                originalLabel.textColor = .white
+                
+                bgView.snp.remakeConstraints { make in
+                    make.top.bottom.equalToSuperview().inset(10)
+                    make.right.equalToSuperview().inset(20)
+                }
+            }
+            else{
+                bgView.backgroundColor = .white
+                originalLabel.textColor = .hexString("#121212")
+                
+                bgView.snp.remakeConstraints { make in
+                    make.top.bottom.equalToSuperview().inset(10)
+                    make.left.equalToSuperview().inset(20)
+                }
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackHistoryVC.swift b/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackHistoryVC.swift
new file mode 100644 (file)
index 0000000..c6e3a5a
--- /dev/null
@@ -0,0 +1,152 @@
+//
+//  FeedbackHistoryVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+import UIKit
+class FeedbackHistoryVC: BaseVC{
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list = [FeedbackHistoryModel]()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+    }
+    override func setupViews() {
+        self.navBar.title = "反馈历史"
+        
+        tableView.do { v in
+            v.backgroundColor = .color(.bgColor)
+            v.delegate = self
+            v.dataSource = self
+            v.rowHeight = 76
+            v.sectionHeaderHeight = 52
+            v.separatorStyle = .none
+            v.register(FeedbackHistoryCell.self, forCellReuseIdentifier: FeedbackHistoryCell.reuseID)
+
+        }
+        
+        view.addSubview(tableView)
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.bottom.equalToSuperview()
+            make.top.equalTo(kNavBtm)
+        }
+    }
+    override func setupData() {
+        let api = CommomApi.feedbackHistory
+        HttpClient.request(api) {[weak self] (model:FeedbackHistoryRoot,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                self.list = model.data
+                self.tableView.reloadData()
+            }
+        }
+    }
+}
+
+extension FeedbackHistoryVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return list.count
+    }
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = FeedbackHistoryCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.model = list[indexPath.row]
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+    }
+}
+
+
+fileprivate class FeedbackHistoryCell: BaseTableViewCell {
+    let bgView = UIView()
+    let icon = UIImageView()
+    let nameLabel = UILabel()
+    let contentLabel = UILabel()
+    let timeLabel = UILabel()
+    
+    var model:FeedbackHistoryModel!{
+        didSet{
+            icon.kf.setImage(with: CacheManager.shared.user.info.avatar.yc.asUrl)
+            nameLabel.text = CacheManager.shared.user.info.userName
+            contentLabel.text = model.description
+            timeLabel.text = model.createTime
+        }
+    }
+    
+    
+    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,timeLabel])
+        
+        backgroundColor = .rgb(0xF7F7F7)
+        contentView.backgroundColor = .clear
+        selectionStyle = .none
+        bgView.do { v in
+            v.backgroundColor = UIColor.white
+            v.yc.setCornerRadius(8)
+        }
+        nameLabel.do { (v) in
+            v.textColor = .hexString("#121212")
+            v.font = .medium(16)
+            v.textAlignment = .left
+            v.lineBreakMode = .byTruncatingTail
+        }
+        contentLabel.do { v in
+            v.textColor = .rgb(0x666666)
+            v.font = .regular(14)
+            v.textAlignment = .left
+            v.lineBreakMode = .byTruncatingTail
+        }
+        timeLabel.do { v in
+            v.textColor = .rgb(0x999999)
+            v.font = .regular(14)
+            v.textAlignment = .right
+        }
+        icon.do { v in
+            v.yc.setCornerRadius(18)
+        }
+    }
+    
+    override func setupLayout() {
+        bgView.snp.makeConstraints { (m) in
+            m.left.right.equalToSuperview().inset(18)
+            m.top.equalToSuperview().inset(10)
+            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(10)
+            m.left.equalToSuperview().offset(60)
+            m.right.lessThanOrEqualToSuperview().inset(150)
+        }
+        contentLabel.snp.makeConstraints { (m) in
+            m.bottom.equalToSuperview().inset(10)
+            m.left.equalToSuperview().offset(60)
+            m.right.lessThanOrEqualToSuperview().inset(16)
+        }
+        timeLabel.snp.makeConstraints { (m) in
+            m.top.equalToSuperview().inset(11)
+            m.right.equalToSuperview().inset(16)
+            
+        }
+    }
+    
+    
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackQaListView.swift b/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackQaListView.swift
new file mode 100644 (file)
index 0000000..44c05de
--- /dev/null
@@ -0,0 +1,88 @@
+//
+//  FeedbackQaListView.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+class FeedbackQaListView: BaseView {
+    let tableView = UITableView(frame: .zero, style: .plain)
+    let vm = FeedbackVM()
+    var didSelected:((_ model:QaModelListModel)->Void)?
+    
+    override func setupViews() {
+        backgroundColor = .white
+        yc.setCornerRadius(12)
+        tableView.do { v in
+            v.delegate = self
+            v.dataSource = self
+            v.separatorStyle = .none
+            v.estimatedRowHeight = 50
+            v.register(FeedbackQaListViewCell.self, forCellReuseIdentifier: FeedbackQaListViewCell.reuseID)
+        }
+        
+        addSubview(tableView)
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(0)
+            make.top.bottom.equalToSuperview().inset(15)
+        }
+    }
+ 
+    func loadData(categoryId:String){
+        vm.categoryId = categoryId
+        vm.refresh {[weak self] model in
+            guard let self = self else{return}
+            if model.isSuccess{
+                tableView.reloadData()
+            }
+        }
+    }
+}
+extension FeedbackQaListView:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return vm.list.count
+    }
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = FeedbackQaListViewCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.titleLabel.text = "\(indexPath.row + 1)." + vm.list[indexPath.row].title
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let data = vm.list[indexPath.row]
+        didSelected?(data)
+    }
+}
+class FeedbackQaListViewCell: BaseTableViewCell {
+    let titleLabel = UILabel()
+
+   
+    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.addSubviews([titleLabel])
+
+        titleLabel.do { (v) in
+            v.textColor = .rgb(0x666666)
+            v.font = .regular(14)
+            v.textAlignment = .left
+            v.numberOfLines = 0
+        }
+    }
+    
+    override func setupLayout() {
+        titleLabel.snp.makeConstraints { (m) in
+            m.top.bottom.equalToSuperview().inset(5)
+            m.left.equalToSuperview().inset(15)
+            m.right.lessThanOrEqualToSuperview().inset(10)
+        }
+    }
+    
+    
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackVM.swift b/ShenYin/ShenYin/UI/Mine/Feedback/FeedbackVM.swift
new file mode 100644 (file)
index 0000000..4490005
--- /dev/null
@@ -0,0 +1,62 @@
+//
+//  FeedbackQaVM.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+class FeedbackVM:BaseVM{
+    var categoryId = ""
+    var list = [QaModelListModel]()
+    var page = 0
+    
+    public func refresh(completion:@escaping (_ result:QaModelListModelroot) ->Void) {
+        self.page = 1
+        let api = CommomApi.qaList(categoryId: categoryId, page: self.page)
+        HttpClient.request(api) {[weak self] (model:QaModelListModelroot,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                page = model.data.currentPage
+                if model.data.currentPage == 1 {
+                    list = model.data.list
+                }
+                else{
+                    list += model.data.list
+                }
+            }
+            completion(model)
+        }
+    }
+    public func loadMore(completion:@escaping (_ result:QaModelListModelroot) ->Void) {
+        self.page += 1
+        let api = CommomApi.qaList(categoryId: categoryId, page: self.page)
+        HttpClient.request(api) {[weak self] (model:QaModelListModelroot,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                page = model.data.currentPage
+                if model.data.currentPage == 1 {
+                    list = model.data.list
+                }
+                else{
+                    list += model.data.list
+                }
+            }
+            completion(model)
+        }
+    }
+}
+
+extension FeedbackVM{
+//    static public func getCagory( completion:@escaping (_ result:MusicCategoryRoot) ->Void) {
+//        let api = CommomApi.musicCategory
+//        HttpClient.request(api) { (model:MusicCategoryRoot,res) in
+//            completion(model)
+//        }
+//    }
+//    static public func getMusicUrl(filedId id:String, completion:@escaping (_ result:MusicDownloadModel) ->Void) {
+//        let api = CommomApi.musicUrl(filedId: id)
+//        HttpClient.request(api) { (model:MusicDownloadModel,res) in
+//            completion(model)
+//        }
+//    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/FeekbackVC.swift b/ShenYin/ShenYin/UI/Mine/Feedback/FeekbackVC.swift
new file mode 100644 (file)
index 0000000..c58b9ef
--- /dev/null
@@ -0,0 +1,75 @@
+//
+//  FeekbackVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/8/6.
+//
+
+import UIKit
+
+class FeekbackVC:BaseVC{
+    let bottomView = FeedbackBottomView()
+    let qaView = FeedbackQaListView()
+    let contentView = FeedbackContentView()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        
+    }
+    override func setupViews() {
+        self.navBar.title = "意见反馈"
+        view.backgroundColor = .color(.bgColor)
+        view.addSubviews([bottomView,qaView,contentView])
+        
+        qaView.do { v in
+            v.didSelected = {[weak self] model in
+                guard let self = self else{return}
+                addModel(model)
+            }
+        }
+        bottomView.do { v in
+            v.feedbackItem.yc.addTap(target: self, action: #selector(feedbackAction))
+            v.chatItem.yc.addTap(target: self, action: #selector(chatAction))
+        }
+    }
+    override func setupLayout() {
+        bottomView.snp.makeConstraints { make in
+            make.left.width.bottom.equalToSuperview()
+            make.height.equalTo(73)
+        }
+        qaView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.height.equalTo(162)
+            make.top.equalToSuperview().inset(150)
+        }
+        contentView.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(18)
+            make.bottom.equalTo(bottomView.snp.top).offset(0)
+            make.top.equalTo(qaView.snp.bottom).offset(20)
+        }
+    }
+    override func setupData() {
+        let api = CommomApi.qaCategory
+        HttpClient.request(api) {[weak self] (model:QaCategoryModelRoot,res) in
+            guard let self = self else {return}
+            if model.isSuccess,let item = model.data.first{
+                self.qaView.loadData(categoryId: item.id)
+            }
+        }
+    }
+    
+    func addModel(_ model:QaModelListModel){
+        contentView.addData(model: FeedbackContentModel(content: model.title,isSend: true))
+        contentView.addData(model: FeedbackContentModel(content: model.content,isSend: false))
+    }
+    
+    @objc func feedbackAction(){
+        App.shared.goToPage(FeedbackCommitVC())
+    }
+    @objc func chatAction(){
+        App.shared.popToRootPage()
+        App.shared.tabController?.selectedIndex = 3
+
+    }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Feedback/QaModel.swift b/ShenYin/ShenYin/UI/Mine/Feedback/QaModel.swift
new file mode 100644 (file)
index 0000000..c9b4af2
--- /dev/null
@@ -0,0 +1,56 @@
+//
+//  QaModel.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/16.
+//
+
+import HandyJSON
+
+class QaCategoryModelRoot:ResponseBaseModel {
+    var data = [QaCategoryModel]()
+}
+class QaCategoryModel:HandyJSON {
+    var id = ""
+    var name = ""
+    required public init() { }
+}
+
+
+class QaModelListModelroot:ResponseBaseModel {
+    var data = QaModelListData()
+}
+class QaModelListData:ResponseBaseModel {
+    var list = [QaModelListModel]()
+    var total = 0
+    var pageSize = 0
+    var currentPage = 0
+    var totalPage = 0
+    var hasNext = false
+    var hasPre = false
+    
+    required public init() { }
+}
+class QaModelListModel:HandyJSON {
+    var id = ""
+    var title = ""
+    var content = ""
+    var categoryId = ""
+    
+    required public init() { }
+}
+
+
+
+class FeedbackHistoryRoot:ResponseBaseModel {
+    var data = [FeedbackHistoryModel]()
+}
+class FeedbackHistoryModel:HandyJSON {
+    var id = ""
+    var type = ""
+    var description = ""
+    var images = ""
+    var phone = ""
+    var createTime = ""
+    required public init() { }
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Instructions/InstructionsDetailVC.swift b/ShenYin/ShenYin/UI/Mine/Instructions/InstructionsDetailVC.swift
new file mode 100644 (file)
index 0000000..eab06cc
--- /dev/null
@@ -0,0 +1,208 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/15.
+//
+
+import UIKit
+import WebKit
+
+
+final class InstructionsDetailVC: BaseVC {
+    let webView = BaseWebView()
+    private let progressView = UIProgressView(progressViewStyle: .default)
+
+    private var productId: String
+    init(productId: String) {
+        self.productId = productId
+        super.init()
+    }
+    
+    override func setupViews() {
+        
+        navBar.title = "电子说明书"
+        
+        view.addSubview(webView)
+        webView.addSubview(progressView)
+        
+        view.backgroundColor = .color(.bgColor)
+        webView.navigationDelegate = self
+        webView.uiDelegate = self
+        webView.backgroundColor = .color(.bgColor)
+        
+        webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
+        webView.addObserver(self, forKeyPath: "canGoBack", options: .new, context: nil)
+        webView.addObserver(self, forKeyPath: "title", options: .new, context: nil)
+        webView.evaluateJavaScript("document.getElementsByTagName('body')[0].style.background='#FAFAFA'", completionHandler: nil)
+        
+        /// 进度条
+        progressView.trackTintColor = .clear
+        progressView.progressTintColor = .color(.theme)
+        progressView.isHidden = false
+        progressView.setProgress(0.05, animated: true)
+    }
+    override func setupLayout() {
+        webView.snp.makeConstraints { (m) in
+            m.top.equalToSuperview().inset(kNavBtm)
+            m.bottom.equalToSuperview()
+            m.leading.trailing.equalToSuperview()
+        }
+        progressView.snp.makeConstraints { (m) in
+            m.top.leading.trailing.equalToSuperview()
+            m.height.equalTo(2)
+        }
+    }
+    
+    override func setupData() {
+        let api = CommomApi.productBook(productId: productId)
+        HttpClient.request(api) {[weak self] (model:InstructionsBookRoot,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                self.dowmload(url: model.data)
+            }
+        }
+    }
+    func dowmload(url:String){
+        HttpClient.download(url: url){[weak self] filePath in
+            guard let self = self else {return}
+            if let path = filePath{
+                let url = URL(fileURLWithPath: path)
+                let request = URLRequest(url: url)
+                webView.load(request)
+            }
+        }
+    }
+    
+    func loadWebData(){
+//        /// 加载URL
+//        if let requestUrl = initData.urlStr.yc.asUrl {
+//            let request = URLRequest(url: requestUrl)
+//            webView.load(request)
+//        }
+//        else if let request = initData.request {
+//            webView.load(request)
+//        }
+//        else { return }
+    }
+    
+    override func navleftViewTapped() {
+        if webView.canGoBack {
+            webView.goBack()
+        }
+        else{
+            if let _ = self.presentingViewController {
+                self.dismiss(animated: true, completion: nil)
+            } else if let nav = self.navigationController {
+                nav.popViewController(animated: true)
+            }
+        }
+        
+    }
+    override func navRightViewTapped() {
+        if let _ = self.presentingViewController {
+            self.dismiss(animated: true, completion: nil)
+        } else if let nav = self.navigationController {
+            nav.popViewController(animated: true)
+        }
+    }
+    
+    deinit {
+        webView.removeObserver(self, forKeyPath: "canGoBack")
+        webView.removeObserver(self, forKeyPath: "estimatedProgress")
+        webView.removeObserver(self, forKeyPath: "title")
+    }
+    
+    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
+        if keyPath == "canGoBack" {
+            if webView.canGoBack {
+                self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
+            } else {
+                self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
+            }
+        } else if keyPath == "estimatedProgress" {
+            progressView.alpha = CGFloat(1.0 - webView.estimatedProgress) + 0.2
+            let progerss = max(Float(webView.estimatedProgress), 0.1)
+            progressView.setProgress(progerss, animated: true)
+            if webView.estimatedProgress >= 1.0 {
+                kDelay(0.3) {
+                    self.progressView.alpha = 0.0
+                    self.progressView.setProgress(0.0, animated: false)
+                }
+            }
+        }else if keyPath == "title" {
+            navBar.title = webView.title ?? " "
+            
+        } else {
+            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
+        }
+    }
+}
+
+// MARK: - ********* webview delegate
+extension InstructionsDetailVC: WKNavigationDelegate {
+    
+    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+        webView.evaluateJavaScript(
+            "document.getElementsByTagName('body')[0].style.background='#FAFAFA'",
+            completionHandler: nil)
+    }
+    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
+        k_log("==== error: \(error)")
+    }
+    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
+        k_log("==== error: \(error)")
+    }
+    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
+        if (navigationAction.targetFrame == nil) {
+            webView.load(navigationAction.request)
+        }
+        guard let policy = WKNavigationActionPolicy(rawValue: WKNavigationActionPolicy.allow.rawValue + 2)
+        else { decisionHandler(.allow); return }
+        decisionHandler(policy)
+    }
+}
+
+// MARK: - WKUIDelegate 不实现该代理方法 网页内调用弹窗时会抛出异常,导致程序崩溃
+extension InstructionsDetailVC: WKUIDelegate {
+    // 获取js 里面的提示
+    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
+        if self.navigationController?.visibleViewController != self {
+            completionHandler()
+            return
+        }
+        let alert = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
+        alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { (_) -> Void in
+            completionHandler()
+        }))
+        alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (_) -> Void in
+            completionHandler()
+        }))
+        self.present(alert, animated: true, completion: nil)
+    }
+    
+    // js 信息的交流
+    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
+        let alert = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
+        alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { (_) -> Void in
+            completionHandler(true)
+        }))
+        alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (_) -> Void in
+            completionHandler(false)
+        }))
+        self.present(alert, animated: true, completion: nil)
+    }
+    
+    // 交互。可输入的文本。
+    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
+        let alert = UIAlertController(title: prompt, message: defaultText, preferredStyle: .alert)
+        alert.addTextField { (textField: UITextField) -> Void in
+            textField.textColor = UIColor.red
+        }
+        alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { (_) -> Void in
+            completionHandler(alert.textFields![0].text!)
+        }))
+        self.present(alert, animated: true, completion: nil)
+    }
+}
+
diff --git a/ShenYin/ShenYin/UI/Mine/Instructions/InstructionsModel.swift b/ShenYin/ShenYin/UI/Mine/Instructions/InstructionsModel.swift
new file mode 100644 (file)
index 0000000..1d0ab11
--- /dev/null
@@ -0,0 +1,27 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/10/15.
+//
+
+import HandyJSON
+
+class InstructionsModelRoot:ResponseBaseModel {
+    var data = [InstructionsModel]()
+}
+class InstructionsModel:HandyJSON {
+    var id = ""
+    var managerId = ""
+    var name = ""
+    var image = ""
+    var description = ""
+    var deleted = ""
+    var createTime = ""
+    var updateTime = ""
+    
+    required public init() { }
+}
+class InstructionsBookRoot:ResponseBaseModel {
+    var data = ""
+}
diff --git a/ShenYin/ShenYin/UI/Mine/Instructions/InstructionsVC.swift b/ShenYin/ShenYin/UI/Mine/Instructions/InstructionsVC.swift
new file mode 100644 (file)
index 0000000..b11bdbd
--- /dev/null
@@ -0,0 +1,73 @@
+//
+//  InstructionsVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/8/26.
+//
+
+import UIKit
+class InstructionsVC: BaseVC{
+    let tableView = UITableView(frame: .zero, style: .plain)
+    var list = [InstructionsModel]()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+    }
+    override func setupViews() {
+        self.navBar.title = "电子说明书"
+        tableView.do { v in
+            v.backgroundColor = .color(.bgColor)
+            v.delegate = self
+            v.dataSource = self
+            v.rowHeight = 52
+            v.sectionHeaderHeight = 52
+            v.separatorStyle = .none
+            v.register(CustomCell1.self, forCellReuseIdentifier: CustomCell1.reuseID)
+            v.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: kScreenHei, height: 20))
+        }
+                
+        view.addSubview(tableView)
+    }
+    override func setupLayout() {
+        tableView.snp.makeConstraints { make in
+            make.left.right.bottom.equalToSuperview()
+            make.top.equalTo(kNavBtm)
+        }
+    }
+    override func setupData() {
+        let api = CommomApi.getProductList
+        HttpClient.request(api) {[weak self] (model:InstructionsModelRoot,res) in
+            guard let self = self else {return}
+            if model.isSuccess{
+                self.list = model.data
+                self.tableView.reloadData()
+            }
+        }
+    }
+}
+
+extension InstructionsVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return list.count
+    }
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = CustomCell1.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: 12, height: 12))
+        }
+        else if indexPath.row == list.count - 1{
+            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.row]
+        App.shared.goToPage(InstructionsDetailVC(productId: data.id))
+    }
+}
index f456ff3..f2d66de 100644 (file)
@@ -8,7 +8,7 @@
 import UIKit
 class MoreSetVC: BaseVC{
     let tableView = UITableView(frame: .zero, style: .plain)
-    let list:[CustomCellType] = [.notice,.language]
+    let list:[CustomCellType] = [.notice,.system]
     
     let switchBtn = CustomButton()
     let logoutBtn = CustomButton()
@@ -52,12 +52,12 @@ class MoreSetVC: BaseVC{
             make.height.equalTo(55)
         }
         logoutBtn.snp.makeConstraints { make in
-            make.bottom.equalTo(deactivateBtn.snp.top).offset(-20)
+            make.bottom.equalTo(deactivateBtn.snp.top).offset(-10)
             make.left.right.equalToSuperview().inset(18)
             make.height.equalTo(55)
         }
         switchBtn.snp.makeConstraints { make in
-            make.bottom.equalTo(logoutBtn.snp.top).offset(-20)
+            make.bottom.equalTo(logoutBtn.snp.top).offset(-10)
             make.left.right.equalToSuperview().inset(18)
             make.height.equalTo(55)
         }
@@ -84,6 +84,18 @@ class MoreSetVC: BaseVC{
             }
         }
     }
+    
+    //MARK: 跳转到APP系统设置权限界面
+     func jumpToSystemPrivacySetting() {
+        guard let appSetting = URL(string: UIApplication.openSettingsURLString) else {
+            return
+        }
+        if #available(iOS 10, *) {
+            UIApplication.shared.open(appSetting, options: [:], completionHandler: nil)
+        } else {
+            UIApplication.shared.openURL(appSetting)
+        }
+    }
 }
 
 extension MoreSetVC:UITableViewDelegate,UITableViewDataSource {
@@ -108,8 +120,8 @@ extension MoreSetVC:UITableViewDelegate,UITableViewDataSource {
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         let data = list[indexPath.row]
         switch data {
-        case .language:
-            App.shared.goToPage(LanguageVC())
+        case .system:
+            jumpToSystemPrivacySetting()
         case .notice:
 //            App.shared.goToPage(SetPhoneVC())
             break
index e4f3286..ee2c094 100644 (file)
@@ -10,6 +10,7 @@ import UIKit
 public enum MineCellType: Int {
     case instructions
     case update
+    case language
     case more
     case feeback
     case about
@@ -20,6 +21,8 @@ public enum MineCellType: Int {
             return "电子说明书"
         case .update:
             return "固件升级"
+        case .language:
+            return "语言设置"
         case .more:
             return "更多设置"
         case .feeback:
@@ -34,6 +37,8 @@ public enum MineCellType: Int {
             return "mine_cell_instru"
         case .update:
             return "mine_cell_update"
+        case .language:
+            return "mine_cell_lan"
         case .more:
             return "mine_cell_more"
         case .feeback:
index 1025bb4..0357c07 100644 (file)
@@ -7,7 +7,7 @@
 import UIKit
 class MineVC: BaseVC{
     let tableView = UITableView(frame: .zero, style: .plain)
-    let list:[MineCellType] = [.instructions,.update,.more,.feeback,.about]
+    let list:[MineCellType] = [.instructions,.update,.language,.more,.feeback,.about]
     let headerView =  MineHeaderView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 266))
     
     override func viewDidLoad() {
@@ -74,6 +74,8 @@ extension MineVC:UITableViewDelegate,UITableViewDataSource {
             App.shared.goToPage(InstructionsVC())
         case .update:
             App.shared.goToPage(UpdateVC())
+        case .language:
+            App.shared.goToPage(LanguageVC())
         case .more:
             App.shared.goToPage(MoreSetVC())
         case .feeback:
index 7367ea4..a5f0957 100644 (file)
@@ -22,7 +22,7 @@ public enum CustomCellType: Int {
     case privacyPolicy
     
     case notice
-    case language
+    case system
     
     case deregister
     
@@ -55,8 +55,8 @@ public enum CustomCellType: Int {
            
         case .notice:
             return "系统消息通知"
-        case .language:
-            return "语言设置"
+        case .system:
+            return "系统权限管理"
             
         case .deregister:
             return "注销账号"
index 22a4586..7874e36 100644 (file)
@@ -130,7 +130,6 @@ class ModeControView: BaseView {
             titleLabel.do { (v) in
                 v.textColor = .rgb(0x666666)
                 v.font = .regular(12)
-                v.text = "降噪"
             }
         }
         
index 8bc1034..8b72ba5 100644 (file)
@@ -29,6 +29,6 @@ class MusicModel:HandyJSON {
     var categoryId = ""
     required public init() { }
 }
-    class MusicDownloadModel:ResponseBaseModel {
-        var data = ""
-    }
+class MusicDownloadModel:ResponseBaseModel {
+    var data = ""
+}
index 47e82e0..b5a14b7 100644 (file)
@@ -40,7 +40,20 @@ class ImagePickerManager: NSObject, TZImagePickerControllerDelegate {
         picker?.modalPresentationStyle = .overFullScreen
         viewController.present(picker!, animated: true, completion: nil)
     }
-    
+    func showImagePicker2(from viewController: UIViewController, callback: @escaping ImagePickerCallback) {
+        self.imageCallback = callback
+        
+        let picker = TZImagePickerController(maxImagesCount: 1, delegate: self)
+//        picker?.needCircleCrop = true
+//        picker?.circleCropRadius = Int(UIScreen.main.bounds.size.width/2)
+//        picker?.allowCrop = true
+        picker?.showSelectBtn = false
+        picker?.allowPickingOriginalPhoto = false
+        picker?.allowPickingVideo = false
+        picker?.allowPickingImage = true
+        picker?.modalPresentationStyle = .overFullScreen
+        viewController.present(picker!, animated: true, completion: nil)
+    }
     // MARK: - 视频选择
     func showVideoPicker(from viewController: UIViewController, callback: @escaping VideoPickerCallback) {
         self.videoCallback = callback
index 8e08442..7b4c6d0 100644 (file)
@@ -72,3 +72,82 @@ class PlaceHolderTextView: BaseView {
         textView.scrollRangeToVisible(range)
     }
 }
+class PlaceHolderTextView2: BaseView,UITextViewDelegate {
+    let bgView = UIView()
+    let textView = UITextView()
+    let label = UILabel()
+    
+    var placeHolde:String?{
+        didSet{
+            label.text = placeHolde
+        }
+    }
+//    var text:String?{
+//        didSet{
+//            textView.text = text
+//            if let text = text,text.isNotEmpty {
+//                label.isHidden = true
+//                return
+//            }
+//            label.isHidden = false
+//        }
+//    }
+    
+    override func setupViews() {
+        backgroundColor = .yellow
+        label.do { v in
+            v.backgroundColor = .clear
+            v.textAlignment = .left
+            v.textColor = .hexString("#999999")
+            v.font = .regular(14)
+            v.numberOfLines = 0
+//            v.backgroundColor = .red
+        }
+        textView.do { v in
+            v.isEditable = true
+            v.font = .regular(14)
+            v.textColor = .hexString("#121212")
+            v.backgroundColor = .clear
+            v.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
+//            v.backgroundColor = .green
+            v.delegate = self
+            v.returnKeyType = .done
+        }
+        addSubviews([bgView])
+        addSubviews([textView,label])
+    }
+
+    
+    deinit {
+        NotificationCenter.default.removeObserver(self, name: UITextView.textDidChangeNotification, object: nil)
+    }
+    override func setupLayout() {
+        bgView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        label.snp.makeConstraints { make in
+            make.left.top.equalToSuperview().inset(6)
+            make.right.lessThanOrEqualToSuperview()
+        }
+        textView.snp.makeConstraints { make in
+            //            make.left.right.equalToSuperview().inset(18)
+            //            make.top.bottom.equalToSuperview().inset(18)
+            make.edges.equalToSuperview()
+        }
+    }
+    
+    func scrollToBottom(){
+        let range = NSMakeRange(textView.text.count - 1, 1)
+        textView.scrollRangeToVisible(range)
+    }
+    func textViewDidChange(_ textView: UITextView) {
+        label.isHidden = textView.text.isNotEmpty
+    }
+    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
+        if text == "\n" {
+            textView.resignFirstResponder()
+            return false
+        }
+        return  true
+    }
+}