充电管理

This commit is contained in:
ddisfriend
2026-07-23 14:45:30 +08:00
parent dab86cff71
commit cc30f59ef9
6 changed files with 509 additions and 12 deletions
+4 -4
View File
@@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
56046A0096E81BD9D3480337 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98591BF58FB3CB969F8A277 /* ScanViewController.swift */; };
5C5E148BC19DCC3A1F56E894 /* libPods-OrderSchedulingNotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D22CCB18DAC27B34DA947D /* libPods-OrderSchedulingNotificationService.a */; }; 5C5E148BC19DCC3A1F56E894 /* libPods-OrderSchedulingNotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F4D22CCB18DAC27B34DA947D /* libPods-OrderSchedulingNotificationService.a */; };
791887422A7CCCCD007EA0C1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 791887402A7CCCCD007EA0C1 /* AppDelegate.swift */; }; 791887422A7CCCCD007EA0C1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 791887402A7CCCCD007EA0C1 /* AppDelegate.swift */; };
791887432A7CCCCD007EA0C1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 791887412A7CCCCD007EA0C1 /* SceneDelegate.swift */; }; 791887432A7CCCCD007EA0C1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 791887412A7CCCCD007EA0C1 /* SceneDelegate.swift */; };
@@ -267,6 +268,7 @@
BD034FA1DD7C17CCBA6B0E5D /* Pods-OrderScheduling.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrderScheduling.release.xcconfig"; path = "Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling.release.xcconfig"; sourceTree = "<group>"; }; BD034FA1DD7C17CCBA6B0E5D /* Pods-OrderScheduling.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrderScheduling.release.xcconfig"; path = "Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling.release.xcconfig"; sourceTree = "<group>"; };
BEFB1D0033356A04B31FA263 /* Pods-OrderScheduling.prerelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrderScheduling.prerelease.xcconfig"; path = "Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling.prerelease.xcconfig"; sourceTree = "<group>"; }; BEFB1D0033356A04B31FA263 /* Pods-OrderScheduling.prerelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrderScheduling.prerelease.xcconfig"; path = "Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling.prerelease.xcconfig"; sourceTree = "<group>"; };
DF9CE87B72CAB01C1BFFDCF9 /* Pods-OrderSchedulingNotificationService.prerelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrderSchedulingNotificationService.prerelease.xcconfig"; path = "Target Support Files/Pods-OrderSchedulingNotificationService/Pods-OrderSchedulingNotificationService.prerelease.xcconfig"; sourceTree = "<group>"; }; DF9CE87B72CAB01C1BFFDCF9 /* Pods-OrderSchedulingNotificationService.prerelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OrderSchedulingNotificationService.prerelease.xcconfig"; path = "Target Support Files/Pods-OrderSchedulingNotificationService/Pods-OrderSchedulingNotificationService.prerelease.xcconfig"; sourceTree = "<group>"; };
E98591BF58FB3CB969F8A277 /* ScanViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = "<group>"; };
F4D22CCB18DAC27B34DA947D /* libPods-OrderSchedulingNotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OrderSchedulingNotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F4D22CCB18DAC27B34DA947D /* libPods-OrderSchedulingNotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OrderSchedulingNotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -426,6 +428,7 @@
children = ( children = (
791887942A80C361007EA0C1 /* WebViewController.swift */, 791887942A80C361007EA0C1 /* WebViewController.swift */,
794FBB1E2A92F7C300D57BB8 /* WebViewTool.swift */, 794FBB1E2A92F7C300D57BB8 /* WebViewTool.swift */,
E98591BF58FB3CB969F8A277 /* ScanViewController.swift */,
); );
path = WebView; path = WebView;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1086,14 +1089,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources.sh\"\n";
@@ -1212,6 +1211,7 @@
79CE24AA2EF52EAF007FCF90 /* IdentityAlertView.swift in Sources */, 79CE24AA2EF52EAF007FCF90 /* IdentityAlertView.swift in Sources */,
79D964E02E77FE2100309946 /* AppealInputViewController.swift in Sources */, 79D964E02E77FE2100309946 /* AppealInputViewController.swift in Sources */,
791887A62A80D50C007EA0C1 /* ParametersList.swift in Sources */, 791887A62A80D50C007EA0C1 /* ParametersList.swift in Sources */,
56046A0096E81BD9D3480337 /* ScanViewController.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -0,0 +1,224 @@
//
// ScanViewController.swift
// OrderScheduling
//
// Created by on 2026/7/23.
//
import Foundation
import UIKit
import AVFoundation
import SnapKit
import DDAutoUIKit_Private
import DDControlsKit_Private
import DDToastKit_Private
class ScanViewController: ZDViewController {
private lazy var session = AVCaptureSession()
private var previewLayer: AVCaptureVideoPreviewLayer?
private var scanCompletion: ((String?) -> Void)?
private let scanRectView = UIView()
private let tipLabel = UILabel()
private let backButton = UIButton(type: .custom)
private let torchButton = UIButton(type: .custom)
init(completion: @escaping (String?) -> Void) {
self.scanCompletion = completion
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
checkPermissionAndSetupCamera()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.isHidden = true
if session.isRunning == false {
DispatchQueue.global(qos: .background).async { [weak self] in
self?.session.startRunning()
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.isHidden = false
if session.isRunning == true {
session.stopRunning()
}
}
private func setupUI() {
view.backgroundColor = .black
scanRectView.layer.borderColor = UIColor.hex("00FF00").cgColor
scanRectView.layer.borderWidth = 1.0
view.addSubview(scanRectView)
tipLabel.text = "将二维码/条码放入框内,即可自动扫描"
tipLabel.textColor = .white
tipLabel.font = UIFont.systemFont(ofSize: 14)
tipLabel.textAlignment = .center
view.addSubview(tipLabel)
backButton.setImage(UIImage(named: "nav_back_white") ?? UIImage(systemName: "chevron.left"), for: .normal)
backButton.tintColor = .white
backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside)
view.addSubview(backButton)
torchButton.setTitle("手电筒", for: .normal)
torchButton.setTitleColor(.white, for: .normal)
torchButton.titleLabel?.font = UIFont.systemFont(ofSize: 14)
torchButton.addTarget(self, action: #selector(torchAction), for: .touchUpInside)
view.addSubview(torchButton)
scanRectView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.height.equalTo(auto(260))
}
tipLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(scanRectView.snp.bottom).offset(auto(20))
make.left.right.equalToSuperview().inset(auto(20))
}
backButton.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(auto(10))
make.left.equalTo(auto(15))
make.width.height.equalTo(auto(44))
}
torchButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(tipLabel.snp.bottom).offset(auto(30))
}
}
private func checkPermissionAndSetupCamera() {
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized:
setupCamera()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
DispatchQueue.main.async {
if granted {
self?.setupCamera()
} else {
self?.showPermissionAlert()
}
}
}
default:
showPermissionAlert()
}
}
private func setupCamera() {
guard let device = AVCaptureDevice.default(for: .video) else {
showErrorAndBack("无法获取相机设备")
return
}
do {
let input = try AVCaptureDeviceInput(device: device)
let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
session.beginConfiguration()
if session.canAddInput(input) {
session.addInput(input)
}
if session.canAddOutput(output) {
session.addOutput(output)
}
output.metadataObjectTypes = [.qr, .ean13, .ean8, .code128, .code39, .code93, .pdf417]
session.commitConfiguration()
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
view.layer.insertSublayer(previewLayer, at: 0)
self.previewLayer = previewLayer
updateScanRect()
DispatchQueue.global(qos: .background).async { [weak self] in
self?.session.startRunning()
}
} catch {
showErrorAndBack("相机初始化失败")
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
previewLayer?.frame = view.bounds
updateScanRect()
}
private func updateScanRect() {
guard let previewLayer = previewLayer else { return }
let rect = scanRectView.frame
let scanRect = previewLayer.metadataOutputRectConverted(fromLayerRect: rect)
if let output = session.outputs.first as? AVCaptureMetadataOutput {
output.rectOfInterest = scanRect
}
}
@objc private func backAction() {
scanCompletion?(nil)
navigationController?.popViewController(animated: true)
}
@objc private func torchAction() {
guard let device = AVCaptureDevice.default(for: .video), device.hasTorch else { return }
do {
try device.lockForConfiguration()
device.torchMode = device.torchMode == .on ? .off : .on
device.unlockForConfiguration()
} catch {}
}
private func showPermissionAlert() {
let alert = UIAlertController(title: "相机权限未开启", message: "请在设置中开启相机权限以使用扫码功能", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "取消", style: .cancel) { [weak self] _ in
self?.scanCompletion?(nil)
self?.navigationController?.popViewController(animated: true)
})
alert.addAction(UIAlertAction(title: "去设置", style: .default) { _ in
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
})
present(alert, animated: true)
}
private func showErrorAndBack(_ message: String) {
view.dd_makeToast(message)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
self?.scanCompletion?(nil)
self?.navigationController?.popViewController(animated: true)
}
}
}
extension ScanViewController: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
guard let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let code = object.stringValue else { return }
session.stopRunning()
scanCompletion?(code)
navigationController?.popViewController(animated: true)
}
}
@@ -13,9 +13,25 @@ import DDLogKit_Private
import DDToastKit_Private import DDToastKit_Private
import DDControlsKit_Private import DDControlsKit_Private
import DDUIKit import DDUIKit
import SafariServices
import CoreLocation
import DDMAMapKit_Private
let WebViewPayCallbackNotification = NSNotification.Name(rawValue: "WebViewPayCallbackNotification")
class WebViewController : ZDViewController { class WebViewController : ZDViewController {
lazy var webView = WKWebView.init() lazy var webView: WKWebView = {
let config = WKWebViewConfiguration()
config.userContentController = WKUserContentController()
//
let script = WKUserScript(source: "window.__IN_APP_WEBVIEW__ = true;", injectionTime: .atDocumentStart, forMainFrameOnly: false)
config.userContentController.addUserScript(script)
let webView = WKWebView(frame: .zero, configuration: config)
return webView
}()
lazy var statusBarBackgroundView = DDView.init() lazy var statusBarBackgroundView = DDView.init()
var showNavBar : Bool var showNavBar : Bool
public var vcTitle : String? public var vcTitle : String?
@@ -23,6 +39,9 @@ class WebViewController : ZDViewController {
var screenEdgePanGestureRecognizerEnable : Bool var screenEdgePanGestureRecognizerEnable : Bool
public var disappearHandler: (() -> Void)? public var disappearHandler: (() -> Void)?
private let locationManager = CLLocationManager()
private var locationCallback: String?
public init(showNavBar:Bool = true,title:String?,url:String,screenEdgePanGestureRecognizerEnable:Bool = true) { public init(showNavBar:Bool = true,title:String?,url:String,screenEdgePanGestureRecognizerEnable:Bool = true) {
self.showNavBar = showNavBar self.showNavBar = showNavBar
self.vcTitle = title self.vcTitle = title
@@ -67,6 +86,11 @@ class WebViewController : ZDViewController {
} }
view.dd_showHUD() view.dd_showHUD()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
NotificationCenter.default.addObserver(self, selector: #selector(handlePayCallback(_:)), name: WebViewPayCallbackNotification, object: nil)
} }
override func viewSafeAreaInsetsDidChange() { override func viewSafeAreaInsetsDidChange() {
@@ -102,7 +126,7 @@ class WebViewController : ZDViewController {
let nav = navigationController as? DDNavigationController let nav = navigationController as? DDNavigationController
nav?.screenEdgePanGestureRecognizerEnable(false) nav?.screenEdgePanGestureRecognizerEnable(false)
} }
webView.configuration.userContentController.add(self, name: "nativeObject") addScriptMessageHandlers()
} }
override func viewWillDisappear(_ animated: Bool) { override func viewWillDisappear(_ animated: Bool) {
@@ -114,7 +138,7 @@ class WebViewController : ZDViewController {
let nav = navigationController as? DDNavigationController let nav = navigationController as? DDNavigationController
nav?.screenEdgePanGestureRecognizerEnable(true) nav?.screenEdgePanGestureRecognizerEnable(true)
} }
webView.configuration.userContentController.removeScriptMessageHandler(forName: "nativeObject") removeScriptMessageHandlers()
} }
override func dd_backActionPop(_ isAnimated: Bool) { override func dd_backActionPop(_ isAnimated: Bool) {
@@ -124,11 +148,79 @@ class WebViewController : ZDViewController {
super.dd_backActionPop(isAnimated) super.dd_backActionPop(isAnimated)
} }
} }
private func addScriptMessageHandlers() {
let handlers = ["nativeObject", "scan", "getLocation", "openMapNavigation", "callPhone", "openDocument"]
handlers.forEach { name in
webView.configuration.userContentController.add(self, name: name)
}
}
private func removeScriptMessageHandlers() {
let handlers = ["nativeObject", "scan", "getLocation", "openMapNavigation", "callPhone", "openDocument"]
handlers.forEach { name in
webView.configuration.userContentController.removeScriptMessageHandler(forName: name)
}
}
@objc private func handlePayCallback(_ notification: Notification) {
guard let orderNo = notification.userInfo?["orderNo"] as? String else { return }
let js = """
(function() {
if (window.onPayCallback) {
window.onPayCallback({ orderNo: '\(orderNo)' });
return true;
}
return false;
})()
"""
webView.evaluateJavaScript(js) { [weak self] result, _ in
if let handled = result as? Bool, handled == true {
return
}
//
self?.webView.reload()
}
}
private func evaluateCallback(_ callback: String?, with result: [String: Any]) {
guard let callback = callback, callback.isEmpty == false else { return }
guard let data = try? JSONSerialization.data(withJSONObject: result, options: []),
let jsonString = String(data: data, encoding: .utf8) else {
return
}
let js = "window['\(callback)'](\(jsonString));"
webView.evaluateJavaScript(js, completionHandler: nil)
}
} }
extension WebViewController : WKScriptMessageHandler { extension WebViewController : WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let dict = message.body as? [String:Any] let dict = message.body as? [String:Any]
let name = message.name
if name == "nativeObject" {
handleNativeObject(dict)
return
}
switch name {
case "scan":
handleScan(dict)
case "getLocation":
handleGetLocation(dict)
case "openMapNavigation":
handleOpenMapNavigation(dict)
case "callPhone":
handleCallPhone(dict)
case "openDocument":
handleOpenDocument(dict)
default:
break
}
}
private func handleNativeObject(_ dict: [String: Any]?) {
let action = dict?["action"] as? String let action = dict?["action"] as? String
if action == "goBack" { if action == "goBack" {
navigationController?.popViewController(animated: true) navigationController?.popViewController(animated: true)
@@ -189,6 +281,124 @@ extension WebViewController : WKScriptMessageHandler {
} }
} }
} }
// MARK: - scan
private func handleScan(_ dict: [String: Any]?) {
let callback = dict?["callback"] as? String
let vc = ScanViewController { [weak self] code in
if let code = code {
self?.evaluateCallback(callback, with: ["success": true, "code": code])
} else {
self?.evaluateCallback(callback, with: ["success": false])
}
}
navigationController?.pushViewController(vc, animated: true)
}
// MARK: - getLocation
private func handleGetLocation(_ dict: [String: Any]?) {
locationCallback = dict?["callback"] as? String
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedWhenInUse, .authorizedAlways:
locationManager.requestLocation()
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
default:
evaluateCallback(locationCallback, with: ["success": false, "error": "location_denied"])
locationCallback = nil
}
}
// MARK: - openMapNavigation
private func handleOpenMapNavigation(_ dict: [String: Any]?) {
let name = dict?["name"] as? String ?? ""
let address = dict?["address"] as? String ?? ""
let latitude = dict?["latitude"] as? Double ?? dict?["lat"] as? Double
let longitude = dict?["longitude"] as? Double ?? dict?["lng"] as? Double
guard let latitude = latitude, let longitude = longitude else {
view.dd_makeToast("缺少导航坐标")
return
}
//
let amapUrlString = "iosamap://route?dlat=\(latitude)&dlon=\(longitude)&dname=\(name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")&dev=0&t=0"
if let amapUrl = URL(string: amapUrlString), UIApplication.shared.canOpenURL(amapUrl) {
UIApplication.shared.open(amapUrl, options: [:], completionHandler: nil)
return
}
// 退 Apple Maps
let appleUrlString = "http://maps.apple.com/?daddr=\(latitude),\(longitude)&q=\(name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
if let appleUrl = URL(string: appleUrlString) {
UIApplication.shared.open(appleUrl, options: [:], completionHandler: nil)
}
}
// MARK: - callPhone
private func handleCallPhone(_ dict: [String: Any]?) {
let callback = dict?["callback"] as? String
guard let phone = dict?["phone"] as? String, phone.isEmpty == false else {
evaluateCallback(callback, with: ["success": false, "error": "phone_empty"])
return
}
let tel = phone.replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "-", with: "")
guard let url = URL(string: "tel://\(tel)") else {
evaluateCallback(callback, with: ["success": false, "error": "phone_invalid"])
return
}
UIApplication.shared.open(url, options: [:]) { [weak self] success in
self?.evaluateCallback(callback, with: ["success": success])
}
}
// MARK: - openDocument
private func handleOpenDocument(_ dict: [String: Any]?) {
guard let urlString = dict?["url"] as? String, let url = URL(string: urlString) else {
view.dd_makeToast("文档地址无效")
return
}
let vc = SFSafariViewController(url: url)
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
}
extension WebViewController : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let result: [String: Any] = [
"success": true,
"latitude": location.coordinate.latitude,
"lat": location.coordinate.latitude,
"y": location.coordinate.latitude,
"longitude": location.coordinate.longitude,
"lng": location.coordinate.longitude,
"x": location.coordinate.longitude,
"lon": location.coordinate.longitude,
"coordType": "wgs84",
"accuracy": location.horizontalAccuracy
]
evaluateCallback(locationCallback, with: result)
locationCallback = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
evaluateCallback(locationCallback, with: ["success": false, "error": error.localizedDescription])
locationCallback = nil
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
if locationCallback != nil {
locationManager.requestLocation()
}
} else if status != .notDetermined, locationCallback != nil {
evaluateCallback(locationCallback, with: ["success": false, "error": "location_denied"])
locationCallback = nil
}
}
} }
private func unwrapEscapedJSONString(_ raw: String) -> String { private func unwrapEscapedJSONString(_ raw: String) -> String {
@@ -245,11 +455,37 @@ extension WebViewController : WKNavigationDelegate,WKUIDelegate {
DDLog(message: "didFailProvisionalNavigation--------------------------------\(String(describing: webView.url?.absoluteString))") DDLog(message: "didFailProvisionalNavigation--------------------------------\(String(describing: webView.url?.absoluteString))")
} }
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) { public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
let urlString = navigationAction.request.url?.absoluteString guard let url = navigationAction.request.url else {
let prefix = "tel:" decisionHandler(.allow)
if let urlString, urlString.hasPrefix(prefix) == true, let tel = urlString.components(separatedBy: prefix).last { return
UIApplication.shared.dd_openUrl(type: .phone, appending: tel)
} }
let urlString = url.absoluteString
let scheme = url.scheme?.lowercased() ?? ""
// tel:
if scheme == "tel", let tel = urlString.components(separatedBy: "tel:").last {
UIApplication.shared.dd_openUrl(type: .phone, appending: tel)
decisionHandler(.cancel)
return
}
// / / App Scheme
let externalSchemes = ["weixin", "wechat", "alipay", "alipays", "iosamap", "baidumap", "qqmap"]
if externalSchemes.contains(scheme) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
return
}
// H5
if scheme == "https", let host = url.host {
let wxHosts = ["wx.tenpay.com", "payapp.wechatpay.com", "mclient.alipay.com", "mobile.alipay.com"]
if wxHosts.contains(host) {
decisionHandler(.allow)
return
}
}
decisionHandler(.allow) decisionHandler(.allow)
} }
} }
+15
View File
@@ -17,6 +17,10 @@
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<true/> <true/>
</dict> </dict>
<key>NSCameraUsageDescription</key>
<string>扫码充电需要访问相机</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>获取附近充电站需要您的位置信息</string>
<key>NSLocationTemporaryUsageDescriptionDictionary</key> <key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict> <dict>
<key>FORCE_FULL_ACCURACY</key> <key>FORCE_FULL_ACCURACY</key>
@@ -39,6 +43,17 @@
</array> </array>
</dict> </dict>
</dict> </dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.zhongdao.orderscheduling.paycallback</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myappscheme</string>
</array>
</dict>
</array>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>remote-notification</string> <string>remote-notification</string>
+17
View File
@@ -39,6 +39,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return .allButUpsideDown return .allButUpsideDown
} }
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
handlePayCallbackURL(url)
return true
}
func applicationDidBecomeActive(_ application: UIApplication) { func applicationDidBecomeActive(_ application: UIApplication) {
JPUSHService.resetBadge() JPUSHService.resetBadge()
UIApplication.shared.applicationIconBadgeNumber = 0 UIApplication.shared.applicationIconBadgeNumber = 0
@@ -166,3 +171,15 @@ extension AppDelegate : JPUSHRegisterDelegate {
} }
} }
func handlePayCallbackURL(_ url: URL) {
guard url.scheme?.lowercased() == "myappscheme" else { return }
guard url.host?.lowercased() == "paycallback" else { return }
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
let orderNo = components?.queryItems?.first(where: { $0.name == "orderNo" })?.value
var userInfo: [String: Any] = [:]
if let orderNo = orderNo {
userInfo["orderNo"] = orderNo
}
NotificationCenter.default.post(name: WebViewPayCallbackNotification, object: nil, userInfo: userInfo)
}
+5
View File
@@ -74,6 +74,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
} }
} }
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
handlePayCallbackURL(url)
}
func sceneDidEnterBackground(_ scene: UIScene) { func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background. // Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information // Use this method to save data, release shared resources, and store enough scene-specific state information