充电管理
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* 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 */; };
|
||||
791887422A7CCCCD007EA0C1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 791887402A7CCCCD007EA0C1 /* AppDelegate.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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -426,6 +428,7 @@
|
||||
children = (
|
||||
791887942A80C361007EA0C1 /* WebViewController.swift */,
|
||||
794FBB1E2A92F7C300D57BB8 /* WebViewTool.swift */,
|
||||
E98591BF58FB3CB969F8A277 /* ScanViewController.swift */,
|
||||
);
|
||||
path = WebView;
|
||||
sourceTree = "<group>";
|
||||
@@ -1086,14 +1089,10 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OrderScheduling/Pods-OrderScheduling-resources.sh\"\n";
|
||||
@@ -1212,6 +1211,7 @@
|
||||
79CE24AA2EF52EAF007FCF90 /* IdentityAlertView.swift in Sources */,
|
||||
79D964E02E77FE2100309946 /* AppealInputViewController.swift in Sources */,
|
||||
791887A62A80D50C007EA0C1 /* ParametersList.swift in Sources */,
|
||||
56046A0096E81BD9D3480337 /* ScanViewController.swift in Sources */,
|
||||
);
|
||||
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,15 +13,34 @@ import DDLogKit_Private
|
||||
import DDToastKit_Private
|
||||
import DDControlsKit_Private
|
||||
import DDUIKit
|
||||
import SafariServices
|
||||
import CoreLocation
|
||||
import DDMAMapKit_Private
|
||||
|
||||
let WebViewPayCallbackNotification = NSNotification.Name(rawValue: "WebViewPayCallbackNotification")
|
||||
|
||||
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()
|
||||
var showNavBar : Bool
|
||||
public var vcTitle : String?
|
||||
var url : String?
|
||||
var screenEdgePanGestureRecognizerEnable : Bool
|
||||
public var disappearHandler: (() -> Void)?
|
||||
|
||||
private let locationManager = CLLocationManager()
|
||||
private var locationCallback: String?
|
||||
|
||||
public init(showNavBar:Bool = true,title:String?,url:String,screenEdgePanGestureRecognizerEnable:Bool = true) {
|
||||
self.showNavBar = showNavBar
|
||||
@@ -67,6 +86,11 @@ class WebViewController : ZDViewController {
|
||||
}
|
||||
|
||||
view.dd_showHUD()
|
||||
|
||||
locationManager.delegate = self
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handlePayCallback(_:)), name: WebViewPayCallbackNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewSafeAreaInsetsDidChange() {
|
||||
@@ -102,7 +126,7 @@ class WebViewController : ZDViewController {
|
||||
let nav = navigationController as? DDNavigationController
|
||||
nav?.screenEdgePanGestureRecognizerEnable(false)
|
||||
}
|
||||
webView.configuration.userContentController.add(self, name: "nativeObject")
|
||||
addScriptMessageHandlers()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
@@ -114,7 +138,7 @@ class WebViewController : ZDViewController {
|
||||
let nav = navigationController as? DDNavigationController
|
||||
nav?.screenEdgePanGestureRecognizerEnable(true)
|
||||
}
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: "nativeObject")
|
||||
removeScriptMessageHandlers()
|
||||
}
|
||||
|
||||
override func dd_backActionPop(_ isAnimated: Bool) {
|
||||
@@ -124,12 +148,80 @@ class WebViewController : ZDViewController {
|
||||
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 {
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
let dict = message.body as? [String:Any]
|
||||
let action = dict?["action"] as? String
|
||||
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
|
||||
if action == "goBack" {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}else if action == "goTraining" {
|
||||
@@ -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 {
|
||||
@@ -245,11 +455,37 @@ extension WebViewController : WKNavigationDelegate,WKUIDelegate {
|
||||
DDLog(message: "didFailProvisionalNavigation--------------------------------\(String(describing: webView.url?.absoluteString))")
|
||||
}
|
||||
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
|
||||
let urlString = navigationAction.request.url?.absoluteString
|
||||
let prefix = "tel:"
|
||||
if let urlString, urlString.hasPrefix(prefix) == true, let tel = urlString.components(separatedBy: prefix).last {
|
||||
UIApplication.shared.dd_openUrl(type: .phone, appending: tel)
|
||||
guard let url = navigationAction.request.url else {
|
||||
decisionHandler(.allow)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>扫码充电需要访问相机</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>获取附近充电站需要您的位置信息</string>
|
||||
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
|
||||
<dict>
|
||||
<key>FORCE_FULL_ACCURACY</key>
|
||||
@@ -39,6 +43,17 @@
|
||||
</array>
|
||||
</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>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
|
||||
@@ -39,6 +39,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
return .allButUpsideDown
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||
handlePayCallbackURL(url)
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
JPUSHService.resetBadge()
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -73,6 +73,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
MCOUNT.newestMessage()
|
||||
}
|
||||
}
|
||||
|
||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||
guard let url = URLContexts.first?.url else { return }
|
||||
handlePayCallbackURL(url)
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
|
||||
Reference in New Issue
Block a user