);
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 = "录音需要使用麦克风";
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协议
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
case let .uploadImage(para):
params += para
break
+ case let .commitFeedback(para):
+ params += para
+ break
default:
break
}
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{
+++ /dev/null
-//
-// 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
- }
- }
-}
-
-
public var isSuccess: Bool { return success}
}
+
+
+class CommonDataModel:ResponseBaseModel {
+ var data = ""
+}
--- /dev/null
+{
+ "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
+ }
+}
--- /dev/null
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "删除@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "删除@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
--- /dev/null
+{
+ "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
+ }
+}
--- /dev/null
+{
+ "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
+ }
+}
--- /dev/null
+{
+ "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
+ }
+}
--- /dev/null
+{
+ "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
+ }
+}
private func getPath()->String{
- let foldPath = NSHomeDirectory() + "/Documents"
+ let foldPath = NSHomeDirectory() + "/Documents/"
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: foldPath) {
do {
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 {
extension App{
var deviceValid:Bool{
- return BleManager.shared.isConnetd
-// return !BleManager.shared.isConnetd
+// return BleManager.shared.isConnetd
+ return !BleManager.shared.isConnetd
}
}
App.shared.window = window
window?.rootViewController = BaseTC()
-// window?.rootViewController = UINavigationController(rootViewController: AudioPlayerVC())
+// window?.rootViewController = UINavigationController(rootViewController: FeedbackCommitVC())
AppLaunch.start()
}
--- /dev/null
+//
+// 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)
+ }
+ }
+
+ }
+}
--- /dev/null
+//
+// 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
+ }
+}
--- /dev/null
+//
+// 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
+ }
+ }
+ }
+}
--- /dev/null
+//
+// 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)
+ })
+ }
+
+}
--- /dev/null
+//
+// 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()
+ }
+ }
+ }
+}
--- /dev/null
+//
+// 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)
+ }
+ }
+ }
+}
--- /dev/null
+//
+// 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)
+ }
+ }
+ }
+ }
+}
--- /dev/null
+//
+// 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)
+
+ }
+ }
+
+
+}
--- /dev/null
+//
+// 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)
+ }
+ }
+
+
+}
--- /dev/null
+//
+// 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)
+// }
+// }
+}
--- /dev/null
+//
+// 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
+
+ }
+}
--- /dev/null
+//
+// 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() { }
+}
--- /dev/null
+//
+// 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)
+ }
+}
+
--- /dev/null
+//
+// 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 = ""
+}
--- /dev/null
+//
+// 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))
+ }
+}
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()
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)
}
}
}
}
+
+ //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 {
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
public enum MineCellType: Int {
case instructions
case update
+ case language
case more
case feeback
case about
return "电子说明书"
case .update:
return "固件升级"
+ case .language:
+ return "语言设置"
case .more:
return "更多设置"
case .feeback:
return "mine_cell_instru"
case .update:
return "mine_cell_update"
+ case .language:
+ return "mine_cell_lan"
case .more:
return "mine_cell_more"
case .feeback:
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() {
App.shared.goToPage(InstructionsVC())
case .update:
App.shared.goToPage(UpdateVC())
+ case .language:
+ App.shared.goToPage(LanguageVC())
case .more:
App.shared.goToPage(MoreSetVC())
case .feeback:
case privacyPolicy
case notice
- case language
+ case system
case deregister
case .notice:
return "系统消息通知"
- case .language:
- return "语言设置"
+ case .system:
+ return "系统权限管理"
case .deregister:
return "注销账号"
titleLabel.do { (v) in
v.textColor = .rgb(0x666666)
v.font = .regular(12)
- v.text = "降噪"
}
}
var categoryId = ""
required public init() { }
}
- class MusicDownloadModel:ResponseBaseModel {
- var data = ""
- }
+class MusicDownloadModel:ResponseBaseModel {
+ var data = ""
+}
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
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
+ }
+}