From f1c64d57f3d028a8087a076bf598c34c7a73b484 Mon Sep 17 00:00:00 2001 From: ddisfriend Date: Mon, 15 Sep 2025 17:36:18 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=B3=E8=AF=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OrderScheduling.xcodeproj/project.pbxproj | 4 + .../HttpRequestCenter/ApiList.swift | 4 + .../HttpRequestCenter/ParametersList.swift | 10 ++ .../HttpRequestCenter/RequestList.swift | 8 + .../HttpResponseModel/ResponseModel.swift | 10 ++ .../AppealInputViewController.swift | 75 +++++++++ .../ReviewFailedController.swift | 147 +++++++++++++++++- 7 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 OrderScheduling/ReviewFailed/ViewController/AppealInputViewController.swift diff --git a/OrderScheduling.xcodeproj/project.pbxproj b/OrderScheduling.xcodeproj/project.pbxproj index 5169ea4..dd7916e 100644 --- a/OrderScheduling.xcodeproj/project.pbxproj +++ b/OrderScheduling.xcodeproj/project.pbxproj @@ -74,6 +74,7 @@ 79CECC222A8A2A2900B95D8B /* VehicleMonitoringController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79CECC212A8A2A2900B95D8B /* VehicleMonitoringController.swift */; }; 79CECC242A8B16D400B95D8B /* VehicleMonitoringListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79CECC232A8B16D400B95D8B /* VehicleMonitoringListController.swift */; }; 79CECC262A8C749B00B95D8B /* VehicleMonitorVideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79CECC252A8C749B00B95D8B /* VehicleMonitorVideoController.swift */; }; + 79D964E02E77FE2100309946 /* AppealInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D964DF2E77FE2100309946 /* AppealInputViewController.swift */; }; 79DD0DAA2A9481BC00768FE7 /* NotificationAuthTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79DD0DA92A9481BC00768FE7 /* NotificationAuthTool.swift */; }; 79DD0DB12A94B3DB00768FE7 /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79DD0DB02A94B3DB00768FE7 /* EmptyView.swift */; }; 79DD0DB42A95F00B00768FE7 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79DD0DB32A95F00B00768FE7 /* Extension.swift */; }; @@ -225,6 +226,7 @@ 79CECCB52A8E04EF00B95D8B /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; 79CECCB62A8E04FA00B95D8B /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 79CECCB72A8E050200B95D8B /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + 79D964DF2E77FE2100309946 /* AppealInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppealInputViewController.swift; sourceTree = ""; }; 79DD0DA12A94501500768FE7 /* OrderScheduling-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OrderScheduling-Bridging-Header.h"; sourceTree = ""; }; 79DD0DA52A945D9E00768FE7 /* OrderSchedulingRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OrderSchedulingRelease.entitlements; sourceTree = ""; }; 79DD0DA62A946B2500768FE7 /* OrderSchedulingDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OrderSchedulingDebug.entitlements; sourceTree = ""; }; @@ -735,6 +737,7 @@ children = ( 79CECC182A89EE6A00B95D8B /* ReviewFailedController.swift */, 79CECC1A2A89F83800B95D8B /* AdditionalPhotoController.swift */, + 79D964DF2E77FE2100309946 /* AppealInputViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -1195,6 +1198,7 @@ 791887AE2A80F943007EA0C1 /* UserData.swift in Sources */, 79FB75EE2A9898EB00DB00A4 /* AcceptOrderView.swift in Sources */, 791887BA2A82495D007EA0C1 /* EnvironmentStrings.swift in Sources */, + 79D964E02E77FE2100309946 /* AppealInputViewController.swift in Sources */, 791887A62A80D50C007EA0C1 /* ParametersList.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/OrderScheduling/HttpRequestCenter/ApiList.swift b/OrderScheduling/HttpRequestCenter/ApiList.swift index 58d3cc2..33718d0 100644 --- a/OrderScheduling/HttpRequestCenter/ApiList.swift +++ b/OrderScheduling/HttpRequestCenter/ApiList.swift @@ -77,4 +77,8 @@ open class ApiList { public let alarmList = "/supplierAppV2/dispatchApp/alarm/alarmList" public let getAlarmByCode = "/supplierAppV2/dispatchApp/alarm/getAlarmByCode" + + public let getAppeal = "/supplierAppV2/dispatchApp/order/getAppeal" + + public let saveAppeal = "/driverApp/task/saveAppeal" } diff --git a/OrderScheduling/HttpRequestCenter/ParametersList.swift b/OrderScheduling/HttpRequestCenter/ParametersList.swift index 8641e38..5777a3c 100644 --- a/OrderScheduling/HttpRequestCenter/ParametersList.swift +++ b/OrderScheduling/HttpRequestCenter/ParametersList.swift @@ -257,3 +257,13 @@ public struct AlarmListParameters : Encodable { public struct GetAlarmByCodeParameters : Encodable { var code : String? } + +public struct GetAppealParameters : Encodable { + var orderCode : String? +} + +public struct SaveAppealParameters : Encodable { + var orderCode : String? + var vehicleId : Int? + var msg : String? +} diff --git a/OrderScheduling/HttpRequestCenter/RequestList.swift b/OrderScheduling/HttpRequestCenter/RequestList.swift index c38f8f4..629de67 100644 --- a/OrderScheduling/HttpRequestCenter/RequestList.swift +++ b/OrderScheduling/HttpRequestCenter/RequestList.swift @@ -160,4 +160,12 @@ open class RequestList { func getAlarmByCode(parameters:P) -> Single?> { return DDAF.post(urlString: HOST+API.getAlarmByCode,parameters: parameters,encoding: URLEncodedFormParameterEncoder.default,headers: [tokenHeader()],responseType: ResponseModel.self) } + + func getAppeal(parameters:P) -> Single?> { + return DDAF.get(urlString: HOST+API.getAppeal,parameters: parameters,encoding: URLEncodedFormParameterEncoder.default,headers: [tokenHeader()],responseType: ResponseModel.self) + } + + func saveAppeal(parameters:P) -> Single?> { + return DDAF.get(urlString: HOST+API.saveAppeal,parameters: parameters,encoding: URLEncodedFormParameterEncoder.default,headers: [tokenHeader()],responseType: ResponseModel.self) + } } diff --git a/OrderScheduling/HttpResponseModel/ResponseModel.swift b/OrderScheduling/HttpResponseModel/ResponseModel.swift index 2827306..a437e4c 100644 --- a/OrderScheduling/HttpResponseModel/ResponseModel.swift +++ b/OrderScheduling/HttpResponseModel/ResponseModel.swift @@ -87,6 +87,8 @@ class OrderListDataModel: Decodable { var supplierSettleRatio : SupplierSettleRatioModel? /// 案件类型 0 传统案件 1 聚合派工 var schedulingFinalRule : Int? + var showAppealBtn : Bool? + var taskVehicleId : Int? class SupplierSettleModel : Decodable { var code : Int var label : String @@ -397,3 +399,11 @@ public class GetVideoUrlDataModel : Decodable { var channelList : [String]? var realtimeList : [String]? } + +public class GetAppealDataModel : Decodable { + var appealReason : String? +} + +public class SaveAppealModel : Decodable { + +} diff --git a/OrderScheduling/ReviewFailed/ViewController/AppealInputViewController.swift b/OrderScheduling/ReviewFailed/ViewController/AppealInputViewController.swift new file mode 100644 index 0000000..1ccb9b7 --- /dev/null +++ b/OrderScheduling/ReviewFailed/ViewController/AppealInputViewController.swift @@ -0,0 +1,75 @@ +// +// AppealInputViewController.swift +// OrderScheduling +// +// Created by 中道 on 2025/9/15. +// + +import UIKit + +final class AppealInputViewController: UIViewController { + + let titleLabel = UILabel() + let textField = UITextField() + + init(prefill: String? = nil) { + super.init(nibName: nil, bundle: nil) + textField.text = prefill + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + view.layer.cornerRadius = 12 + view.clipsToBounds = true + + // 标题 + titleLabel.text = "申诉理由" + titleLabel.textAlignment = .center + titleLabel.font = .systemFont(ofSize: 16, weight: .semibold) + titleLabel.textColor = .black + + // 输入框 + textField.placeholder = "请输入申诉原因…" + textField.borderStyle = .none + textField.backgroundColor = UIColor(white: 0.98, alpha: 1) + textField.layer.cornerRadius = 10 + textField.layer.borderWidth = 1 + textField.layer.borderColor = UIColor.systemBlue.withAlphaComponent(0.35).cgColor + textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10)) + textField.leftViewMode = .always + textField.clearButtonMode = .whileEditing + textField.returnKeyType = .done + textField.delegate = self + + let container = UIStackView(arrangedSubviews: [titleLabel, textField]) + container.axis = .vertical + container.spacing = 14 + container.alignment = .fill + container.distribution = .fill + view.addSubview(container) + container.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + container.topAnchor.constraint(equalTo: view.topAnchor, constant: 16), + container.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), + container.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), + container.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -16), + textField.heightAnchor.constraint(equalToConstant: 40) + ]) + } + + // 控制弹窗内容的尺寸(宽度由库控制,这里给出理想高度) + override var preferredContentSize: CGSize { + get { CGSize(width: 280, height: 140) } + set { super.preferredContentSize = newValue } + } +} + +extension AppealInputViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } +} diff --git a/OrderScheduling/ReviewFailed/ViewController/ReviewFailedController.swift b/OrderScheduling/ReviewFailed/ViewController/ReviewFailedController.swift index bce7bbe..5777c86 100644 --- a/OrderScheduling/ReviewFailed/ViewController/ReviewFailedController.swift +++ b/OrderScheduling/ReviewFailed/ViewController/ReviewFailedController.swift @@ -14,6 +14,7 @@ import RxRelay import RxCocoa import MJRefresh import ESTabBarController_swift +import PopupDialog extension ReviewFailedController { func addActions() { @@ -196,11 +197,53 @@ extension ReviewFailedController : UITableViewDelegate,UITableViewDataSource { }) .disposed(by: cell!.disposeBag) + cell!.appealButton.rx.tap + .flatMapLatest({ _ in + return RQ.getAppeal(parameters: GetAppealParameters(orderCode: model.orderCode)) + }) + .flatMapLatest({ [weak self] response -> Observable in + // 若已有申诉,提示并结束流 + guard response?.data == nil else { + DispatchQueue.main.async { + self?.view.dd_makeToast("该订单正在申诉中") + } + return .empty() + } + // 否则弹出输入框,异步拿到申诉理由 + return Observable.create { ob in + guard let self = self else { return Disposables.create() } + DispatchQueue.main.async { + self.presentAppealPopup(from: self) { reason in + ob.onNext(reason) + ob.onCompleted() + } + } + return Disposables.create() + } + }) + .flatMapLatest({ reason in + return RQ.saveAppeal(parameters: SaveAppealParameters(orderCode: model.orderCode, vehicleId: model.taskVehicleId, msg: reason)) + }) + .observe(on: ConcurrentMainScheduler.instance) + .subscribe(onNext: {[weak self] response in + if response?.success == true { + }else{ + self?.view.dd_makeToast(response?.msg) + } + }) + .disposed(by: cell!.disposeBag) + if USERP.canSupplierAuditUploadPhotoBtn == true { cell?.additionalButton.isHidden = false }else{ cell?.additionalButton.isHidden = true } + + if model.showAppealBtn == true { + cell?.appealButton.isHidden = false + }else{ + cell?.appealButton.isHidden = true + } return cell! } @@ -314,6 +357,66 @@ open class ReviewFailedController : ZDViewController { super.reloadData() preRefreshRelay.accept(nil) } + + /// 弹出“申诉理由”输入弹窗 + /// - Parameters: + /// - from: 当前控制器 + /// - prefill: 预填内容 + /// - onConfirm: 点击【确定】回调(返回填写的文本) + func presentAppealPopup(from: UIViewController, + prefill: String? = nil, + onConfirm: @escaping (String) -> Void) { + + // 内容 VC + let inputVC = AppealInputViewController(prefill: prefill) + + // 弹窗 + let popup = PopupDialog( + viewController: inputVC, + buttonAlignment: .horizontal, // 底部按钮水平排列 + transitionStyle: .bounceDown, // 动画风格 + tapGestureDismissal: false, // 点击外部不关闭 + panGestureDismissal: false // 禁止下拉关闭(避免误触) + ) + + // 取消 + let cancel = CancelButton(title: "取消") { + // do nothing + } + + // 确定 + let ok = DefaultButton(title: "确定", dismissOnTap: false) { + let text = inputVC.textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + guard text.isEmpty == false else { + // 简单的必填校验(也可改成 toast / 抖动) + inputVC.textField.layer.borderColor = UIColor.systemRed.withAlphaComponent(0.6).cgColor + UIImpactFeedbackGenerator(style: .light).impactOccurred() + return + } + popup.dismiss() { + onConfirm(text) + } + } + + popup.addButtons([cancel, ok]) + + // (可选)统一外观:分割线/按钮颜色/标题样式 + let pv = PopupDialogDefaultView.appearance() + pv.titleFont = .systemFont(ofSize: 16, weight: .semibold) + pv.titleTextAlignment = .center + pv.titleColor = .black + pv.messageTextAlignment = .center + + let cb = CancelButton.appearance() + cb.titleColor = UIColor.darkText + cb.separatorColor = UIColor.systemGray5 + + let db = DefaultButton.appearance() + db.titleColor = UIColor.systemBlue + db.separatorColor = UIColor.systemGray5 + + from.present(popup, animated: true) + } } open class ReviewFailedView : DDView { @@ -343,6 +446,18 @@ open class ReviewFailedCell : DDTableViewCell { public let orderStatusLabel : DDLabel public let descLabel : DDLabel public let dateLabel : DDLabel + public let stackView : UIStackView + public let appealButton : DDButton + public var appealLayer : CAGradientLayer = { + var layer = CAGradientLayer.init() + layer.startPoint = CGPoint(x: 0, y: 0) + layer.endPoint = CGPoint(x: 1, y: 1) + layer.locations = [0.0,1.0] + layer.colors = [UIColor.hex("FF5A2C").cgColor,UIColor.hex("FE9D4D").cgColor] + layer.cornerRadius = auto(4) + layer.masksToBounds = true + return layer + }() public let additionalButton : DDButton public var additionalLayer : CAGradientLayer = { var layer = CAGradientLayer.init() @@ -364,7 +479,9 @@ open class ReviewFailedCell : DDTableViewCell { orderStatusLabel = DDLabel.dd_init(withText: "", font: .mediumFont(auto(12)), textColor: .hex("E69B0B")) descLabel = DDLabel.dd_init(withText: "", font: .mediumFont(auto(13)), textColor: .hex("FF8F37")) dateLabel = DDLabel.dd_init(withText: "", font: .regularFont(auto(12)), textColor: .hex("000000").alpha(0.55)) + stackView = UIStackView() additionalButton = DDButton.dd_initCustom() + appealButton = DDButton.dd_initCustom() super.init(style: style, reuseIdentifier: reuseIdentifier) backgroundColor = .hex("F4F5F7") @@ -385,12 +502,26 @@ open class ReviewFailedCell : DDTableViewCell { descLabel.numberOfLines = 0 radiusView.addSubview(descLabel) radiusView.addSubview(dateLabel) + stackView.axis = .horizontal + stackView.distribution = .equalSpacing + stackView.alignment = .center + stackView.spacing = 10 + radiusView.addSubview(stackView) + appealButton.setTitle("申诉", for: .normal) + appealButton.setTitleColor(.white, for: .normal) + appealButton.titleLabel?.font = .mediumFont(auto(13)) + appealButton.layer.cornerRadius = auto(4) + appealButton.layer.masksToBounds = true + appealButton.contentEdgeInsets = UIEdgeInsets(top: auto(6), left: auto(12), bottom: auto(6), right: auto(12)) + stackView.addArrangedSubview(appealButton) + appealButton.layer.insertSublayer(appealLayer, at: 0) additionalButton.setTitle("补充", for: .normal) additionalButton.setTitleColor(.white, for: .normal) additionalButton.titleLabel?.font = .mediumFont(auto(13)) additionalButton.layer.cornerRadius = auto(4) additionalButton.layer.masksToBounds = true - radiusView.addSubview(additionalButton) + additionalButton.contentEdgeInsets = UIEdgeInsets(top: auto(6), left: auto(12), bottom: auto(6), right: auto(12)) + stackView.addArrangedSubview(additionalButton) additionalButton.layer.insertSublayer(additionalLayer, at: 0) radiusView.snp.makeConstraints { make in @@ -432,11 +563,11 @@ open class ReviewFailedCell : DDTableViewCell { make.bottom.equalTo(-auto(15)) } - additionalButton.snp.makeConstraints { make in - make.right.equalTo(-auto(10)) + stackView.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-auto(10)) make.centerY.equalTo(dateLabel) - make.width.equalTo(auto(50)) - make.height.equalTo(auto(20)) + make.height.equalTo(auto(50)) + make.left.greaterThanOrEqualTo(dateLabel.snp.right).offset(auto(10)) } } @@ -447,10 +578,14 @@ open class ReviewFailedCell : DDTableViewCell { open override func prepareForReuse() { super.prepareForReuse() disposeBag = DisposeBag() + appealButton.isHidden = true + additionalButton.isHidden = true } open override func layoutSubviews() { super.layoutSubviews() - additionalLayer.frame = CGRectMake(0, 0, auto(50), auto(20)) + // Match gradient layers to button bounds + appealLayer.frame = appealButton.bounds + additionalLayer.frame = additionalButton.bounds } }