Commit b1a4dff7 authored by Steven杜宇's avatar Steven杜宇

Merge branch 'qrcode' into salon

# Conflicts:
#	galaxy/galaxy.xcodeproj/project.pbxproj
parents 4c2a8616 b23feb87
...@@ -171,8 +171,6 @@ target 'galaxy' do ...@@ -171,8 +171,6 @@ target 'galaxy' do
pod 'xxtea', '1.0.2' pod 'xxtea', '1.0.2'
#代码规范 #代码规范
pod 'SwiftLint', :configurations => ['Debug'] pod 'SwiftLint', :configurations => ['Debug']
# 跟踪app性能
pod 'Sentry'
#腾讯IM #腾讯IM
tencentIM_pods tencentIM_pods
#腾讯客服 #腾讯客服
......
This diff is collapsed.
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
// //
import UIKit import UIKit
import Sentry
import ESTabBarController_swift import ESTabBarController_swift
import IQKeyboardManagerSwift import IQKeyboardManagerSwift
...@@ -30,7 +29,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, WXApiDelegate { ...@@ -30,7 +29,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, WXApiDelegate {
var window: UIWindow? var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
configSentry()
Thread.sleep(forTimeInterval: 0.5) // 应产品同学Nick要求 启动页时间展示长点 Thread.sleep(forTimeInterval: 0.5) // 应产品同学Nick要求 启动页时间展示长点
setupAudionConfig() setupAudionConfig()
...@@ -469,38 +467,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, WXApiDelegate { ...@@ -469,38 +467,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, WXApiDelegate {
extension AppDelegate { extension AppDelegate {
private func configSentry() {
SentrySDK.start { options in
options.dsn = "https://41235d99f7163a287785964dae61d5a9@dev-sentry.galaxy-immi.com/7"
// Adds IP for users.
// For more information, visit: https://docs.sentry.io/platforms/apple/data-management/data-collected/
options.sendDefaultPii = true
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = 1.0
// Configure profiling. Visit https://docs.sentry.io/platforms/apple/profiling/ to learn more.
options.configureProfiling = {
$0.sessionSampleRate = 1.0 // We recommend adjusting this value in production.
$0.lifecycle = .trace
}
#if DEBUG || TESTENV
// Uncomment the following lines to add more data to your events
options.attachScreenshot = true // This adds a screenshot to the error events
options.attachViewHierarchy = true // This adds the view hierarchy to the error events
options.debug = true // Enabled debug when first installing is always helpful
options.environment = "debug"
#else
options.debug = false // Enabled debug when first installing is always helpful
options.environment = "production"
options.attachViewHierarchy = true
#endif
}
// Remove the next line after confirming that your Sentry integration is working.
// SentrySDK.capture(message: "This app uses Sentry! :)")
}
private func thirdSetting() { private func thirdSetting() {
// 1.数据解析 // 1.数据解析
#if DEBUG #if DEBUG
......
//
// YHSignSuccessActivity.swift
// galaxy
//
// Created by Dufet on 2025/10/15.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
class YHSignSuccessActivity: NSObject {
}
...@@ -136,6 +136,36 @@ private extension YHHomeKingKongBlockView { ...@@ -136,6 +136,36 @@ private extension YHHomeKingKongBlockView {
case 7: case 7:
// 生意星 // 生意星
gotoBusinessStar() gotoBusinessStar()
case 8:
// 银河新品
if !YHLoginManager.shared.isLogin() {
YHOneKeyLoginManager.shared.oneKeyLogin()
return
}
var url = YHBaseUrlManager.shared.businessH5Url() + "/yh-new-goods/detail"
if YHLoginManager.shared.isLogin() {
let token = YHLoginManager.shared.h5Token
let urlHasParam = String.hasQueryParameters(urlString: url)
if urlHasParam {
url = url + "&param=" + token
} else {
url = url + "?param=" + token
}
}
// 2.增加导航栏高度 https://test-hklife.galaxy-immi.com/superAppBridge.html#/goods/sales-detail?id=
var tUrl = url
if !url.contains("navigationH=") {
tUrl = url + "?navigationH=\(k_Height_NavigationtBarAndStatuBar)"
if url.contains("?") {
tUrl = url + "&navigationH=\(k_Height_NavigationtBarAndStatuBar)"
}
}
let vc = YHH5WebViewVC()
vc.url = tUrl
UIViewController.current?.navigationController?.pushViewController(vc)
default: default:
break break
} }
......
//
// YHNewProductCell.swift
// galaxy
//
// Created by Dufet on 2025/10/12.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
class YHNewProductCell: UITableViewCell {
static let cellReuseIdentifier = "YHNewProductCell"
var model: YHSelectGoodsModel = YHSelectGoodsModel()
var onImageHeightChanged: (() -> Void)?
lazy var imgView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleAspectFill
v.clipsToBounds = true
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateModel(_ model: YHSelectGoodsModel) {
self.model = model
if let url = URL(string: model.image) {
imgView.sd_setImage(with: url, placeholderImage: UIImage(named: "plan_product_default")) { [weak self] image, _, _, resultUrl in
guard let self = self else {
return
}
guard resultUrl?.absoluteString == self.model.image else {
return
}
guard let image = image, image.size.width > 0, image.size.height > 0 else {
return
}
model.ratio = image.size.height / image.size.width
self.imgView.snp.updateConstraints { make in
make.height.equalTo(model.ratio * (KScreenWidth-20.0*2))
}
self.onImageHeightChanged?()
}
} else {
imgView.snp.updateConstraints { make in
make.height.equalTo(YHSelectGoodsModel.fix_new_productRatio * (KScreenWidth-20.0*2))
}
}
}
func setupUI() {
self.selectionStyle = .none
self.contentView.addSubview(self.imgView)
self.imgView.snp.makeConstraints { make in
make.left.equalTo(20)
make.right.equalTo(-20)
make.top.equalToSuperview()
make.height.equalTo(YHSelectGoodsModel.fix_new_productRatio * (KScreenWidth-20.0*2))
make.bottom.equalTo(-12)
}
}
}
...@@ -127,6 +127,7 @@ extension YHSelectViewController { ...@@ -127,6 +127,7 @@ extension YHSelectViewController {
head.block = { index in head.block = { index in
self.selectItem = index self.selectItem = index
self.buttonItem = 0 self.buttonItem = 0
self.tableView.reloadData()
self.updataData() self.updataData()
} }
head.buttonBlock = { index in head.buttonBlock = { index in
...@@ -147,7 +148,9 @@ extension YHSelectViewController { ...@@ -147,7 +148,9 @@ extension YHSelectViewController {
tableView.delegate = self tableView.delegate = self
tableView.tableHeaderView = sectionView tableView.tableHeaderView = sectionView
tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: KScreenWidth, height: 250)) tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: KScreenWidth, height: 250))
tableView.register(cellWithClass: YHSelectLookTableViewCell.self) tableView.register(YHSelectLookTableViewCell.self, forCellReuseIdentifier: YHSelectLookTableViewCell.cellReuseIdentifier)
tableView.register(YHNewProductCell.self, forCellReuseIdentifier: YHNewProductCell.cellReuseIdentifier)
return tableView return tableView
}() }()
view.addSubview(tableView) view.addSubview(tableView)
...@@ -185,13 +188,36 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource { ...@@ -185,13 +188,36 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource {
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withClass: YHSelectLookTableViewCell.self)
let model = self.viewModel.goodsArray[indexPath.row]
if model.goodsCategoryType == 1 { // 新品商品
guard let cell = tableView.dequeueReusableCell(withIdentifier: YHNewProductCell.cellReuseIdentifier, for: indexPath) as? YHNewProductCell else {
return UITableViewCell()
}
cell.updateModel(model)
cell.onImageHeightChanged = { [weak self] in
guard let self = self else { return }
UIView.performWithoutAnimation {
self.tableView.reloadRows(at: [indexPath], with: .none)
}
}
return cell
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: YHSelectLookTableViewCell.cellReuseIdentifier, for: indexPath) as? YHSelectLookTableViewCell else {
return UITableViewCell()
}
cell.number = indexPath.row cell.number = indexPath.row
cell.dataSource = self.viewModel.goodsArray[indexPath.row] cell.dataSource = model
return cell return cell
} }
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let model = self.viewModel.goodsArray[indexPath.row]
if model.goodsCategoryType == 1 { // 新品商品
return UITableView.automaticDimension
}
if indexPath.row == 0 { if indexPath.row == 0 {
return 102 return 102
} }
...@@ -199,6 +225,9 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource { ...@@ -199,6 +225,9 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource {
} }
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if selectItem == 0 {
return 46
}
return 66 return 66
} }
...@@ -208,6 +237,7 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource { ...@@ -208,6 +237,7 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource {
// headView.itemButton.setTitle(model.name, for: .normal) // headView.itemButton.setTitle(model.name, for: .normal)
// headView.itemButton.iconInRight(with: 0.5) // headView.itemButton.iconInRight(with: 0.5)
// } // }
return headView return headView
} }
...@@ -221,8 +251,13 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource { ...@@ -221,8 +251,13 @@ extension YHSelectViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let model = self.viewModel.goodsArray[indexPath.row] let model = self.viewModel.goodsArray[indexPath.row]
var url = YHBaseUrlManager.shared.curH5URL() + "superAppBridge.html#/goods/sales-detail" + "?id=\(model.id)" var url = ""
YHAnalytics.track("APP_GalaxySelectionPage_ClickProduct", properties: ["productID": model.id]) if model.goodsCategoryType == 1 { // 新品商品
url = YHBaseUrlManager.shared.businessH5Url() + "/yh-new-goods/detail?id=" + model.id
} else {
url = YHBaseUrlManager.shared.curH5URL() + "superAppBridge.html#/goods/sales-detail" + "?id=\(model.id)"
YHAnalytics.track("APP_GalaxySelectionPage_ClickProduct", properties: ["productID": model.id])
}
if YHLoginManager.shared.isLogin() { if YHLoginManager.shared.isLogin() {
let token = YHLoginManager.shared.h5Token let token = YHLoginManager.shared.h5Token
...@@ -264,6 +299,9 @@ extension YHSelectViewController: JXSegmentedListContainerViewListDelegate { ...@@ -264,6 +299,9 @@ extension YHSelectViewController: JXSegmentedListContainerViewListDelegate {
} }
class YHSelectLookTableViewCell: UITableViewCell { class YHSelectLookTableViewCell: UITableViewCell {
static let cellReuseIdentifier = "YHSelectLookTableViewCell"
var centerImageView: UIImageView! var centerImageView: UIImageView!
var titleLabel: UILabel! var titleLabel: UILabel!
var subTitleLabel: UILabel! var subTitleLabel: UILabel!
......
...@@ -35,6 +35,8 @@ class YHSelectGoodModel: SmartCodable { ...@@ -35,6 +35,8 @@ class YHSelectGoodModel: SmartCodable {
class YHSelectGoodsModel: SmartCodable { class YHSelectGoodsModel: SmartCodable {
static let fix_new_productRatio = 160.0/335
var id: String = "" var id: String = ""
var name: String = "" var name: String = ""
var products: [Int] = [] var products: [Int] = []
...@@ -64,8 +66,13 @@ class YHSelectGoodsModel: SmartCodable { ...@@ -64,8 +66,13 @@ class YHSelectGoodsModel: SmartCodable {
var icons: [YHSelectGoodIconModel] = [] var icons: [YHSelectGoodIconModel] = []
// 货币符号 HKD、CNY // 货币符号 HKD、CNY
var currency: String = "" var currency: String = ""
var goodsCategoryType: Int = 0 // 产品的分类类型:0 默认普通,1 新品商品
// 新品图片宽高比
var ratio: CGFloat = YHSelectGoodsModel.fix_new_productRatio
required init() { required init() {
} }
} }
......
...@@ -21,6 +21,7 @@ class YHSelectLookHeadView: UIView { ...@@ -21,6 +21,7 @@ class YHSelectLookHeadView: UIView {
normalButton.isSelected = true normalButton.isSelected = true
hotButton.isSelected = false hotButton.isSelected = false
priceButton.isSelected = false priceButton.isSelected = false
} else if buttonIndex == 1 { } else if buttonIndex == 1 {
normalButton.isSelected = false normalButton.isSelected = false
hotButton.isSelected = true hotButton.isSelected = true
...@@ -99,6 +100,7 @@ class YHSelectLookHeadView: UIView { ...@@ -99,6 +100,7 @@ class YHSelectLookHeadView: UIView {
// button.setImage(UIImage(named: "home_select_icon_select"), for: .selected) // button.setImage(UIImage(named: "home_select_icon_select"), for: .selected)
button.addTarget(self, action: #selector(normalClick), for: .touchUpInside) button.addTarget(self, action: #selector(normalClick), for: .touchUpInside)
button.isSelected = true button.isSelected = true
button.isHidden = true
return button return button
}() }()
addSubview(normalButton) addSubview(normalButton)
...@@ -118,6 +120,7 @@ class YHSelectLookHeadView: UIView { ...@@ -118,6 +120,7 @@ class YHSelectLookHeadView: UIView {
button.setTitleColor(UIColor.brandMainColor, for: .selected) button.setTitleColor(UIColor.brandMainColor, for: .selected)
// button.setImage(UIImage(named: "home_select_icon_select"), for: .selected) // button.setImage(UIImage(named: "home_select_icon_select"), for: .selected)
button.addTarget(self, action: #selector(hotClick), for: .touchUpInside) button.addTarget(self, action: #selector(hotClick), for: .touchUpInside)
button.isHidden = true
return button return button
}() }()
addSubview(hotButton) addSubview(hotButton)
...@@ -137,6 +140,7 @@ class YHSelectLookHeadView: UIView { ...@@ -137,6 +140,7 @@ class YHSelectLookHeadView: UIView {
button.setTitleColor(UIColor.brandMainColor, for: .selected) button.setTitleColor(UIColor.brandMainColor, for: .selected)
button.setImage(UIImage(named: "home_select_icon_select"), for: .selected) button.setImage(UIImage(named: "home_select_icon_select"), for: .selected)
button.addTarget(self, action: #selector(priceClick), for: .touchUpInside) button.addTarget(self, action: #selector(priceClick), for: .touchUpInside)
button.isHidden = true
return button return button
}() }()
addSubview(priceButton) addSubview(priceButton)
...@@ -213,6 +217,14 @@ extension YHSelectLookHeadView: UICollectionViewDelegate, UICollectionViewDataSo ...@@ -213,6 +217,14 @@ extension YHSelectLookHeadView: UICollectionViewDelegate, UICollectionViewDataSo
collectionView.reloadData() collectionView.reloadData()
if let block = block { if let block = block {
block(indexPath.row) block(indexPath.row)
normalButton.isHidden = false
hotButton.isHidden = false
priceButton.isHidden = false
if indexPath.row == 0 {
normalButton.isHidden = true
hotButton.isHidden = true
priceButton.isHidden = true
}
} }
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
} }
......
...@@ -8,22 +8,44 @@ ...@@ -8,22 +8,44 @@
import UIKit import UIKit
// MARK: - 扫码控制器
class YHScanViewController: LBXScanViewController { class YHScanViewController: LBXScanViewController {
// MARK: - Properties
private let topOffset = 44.0 private let topOffset = 44.0
private let leftOffset = 48.0 private let leftOffset = 48.0
/// 扫码支持的类型(可以是单个或多个类型的组合)
var scanTypes: YHScanType? {
didSet {
updateUIForScanType()
}
}
/// 扫码成功回调
var scanCompletion: YHScanCompletion?
/// 提示文字标签
private lazy var instructionLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor(hexString: "#EBF0F9")
label.font = .PFSC_M(ofSize: 18)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
// MARK: - Lifecycle
override var preferredStatusBarStyle: UIStatusBarStyle { override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent return .lightContent
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
gk_navTitleColor = .white setupNavigationBar()
gk_navTitle = "扫码"
gk_navBarAlpha = 0.0
gk_navBackgroundColor = .clear
gk_backImage = UIImage(named: "back_icon_white")
setupScanStyle() setupScanStyle()
setupInstructionLabel()
updateUIForScanType()
} }
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
...@@ -32,20 +54,73 @@ class YHScanViewController: LBXScanViewController { ...@@ -32,20 +54,73 @@ class YHScanViewController: LBXScanViewController {
if view.subviews.contains(navBar) { if view.subviews.contains(navBar) {
view.bringSubviewToFront(navBar) view.bringSubviewToFront(navBar)
} }
if view.subviews.contains(instructionLabel) {
view.bringSubviewToFront(instructionLabel)
}
} }
// override func handleCodeResult(arrayResult: [LBXScanResult]) { override func handleCodeResult(arrayResult: [LBXScanResult]) {
// // guard let firstResult = arrayResult.first,
// } let code = firstResult.strScanned else {
return
}
guard let scanTypes = scanTypes else {
super.handleCodeResult(arrayResult: arrayResult)
return
}
// 停止扫描
stopScan()
// 识别二维码类型
let recognizedType = scanTypes.recognizeType(from: code)
// 验证类型
if recognizedType == nil && scanTypes != .all && scanTypes.rawValue != 0 {
// 类型不匹配,显示错误提示
showError(message: "二维码类型不匹配,请扫描正确的二维码")
// 延迟后重新开始扫描
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
self?.startScan()
}
return
}
// 震动反馈
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
// 创建结果
let result = YHScanResult(
code: code,
recognizedType: recognizedType,
supportedTypes: scanTypes
)
// 回调处理
if let completion = scanCompletion {
completion(result)
navigationController?.popViewController(animated: true)
} else {
// 如果没有设置回调,使用默认处理
handleDefaultResult(result)
}
}
deinit { deinit {
// print("YHScanViewController deinit")
} }
} }
// MARK: - Setup
extension YHScanViewController { extension YHScanViewController {
private func setupNavigationBar() {
gk_navTitleColor = .white
gk_navBarAlpha = 0.0
gk_navBackgroundColor = .clear
gk_backImage = UIImage(named: "back_icon_white")
}
private func setupScanStyle() { private func setupScanStyle() {
var style = LBXScanViewStyle() var style = LBXScanViewStyle()
style.centerUpOffset = topOffset style.centerUpOffset = topOffset
...@@ -56,13 +131,155 @@ extension YHScanViewController { ...@@ -56,13 +131,155 @@ extension YHScanViewController {
style.photoframeAngleH = 18 style.photoframeAngleH = 18
style.isNeedShowRetangle = false style.isNeedShowRetangle = false
style.anmiationStyle = LBXScanViewAnimationStyle.LineMove style.anmiationStyle = LBXScanViewAnimationStyle.LineMove
// 扫描横线图片
style.animationImage = UIImage(named: "scan_move_line") style.animationImage = UIImage(named: "scan_move_line")
// 4个角的颜色
style.colorAngle = UIColor.white style.colorAngle = UIColor.white
// 非矩形框区域颜色
style.color_NotRecoginitonArea = UIColor.black.withAlphaComponent(0.3) style.color_NotRecoginitonArea = UIColor.black.withAlphaComponent(0.3)
scanStyle = style scanStyle = style
} }
private func setupInstructionLabel() {
view.addSubview(instructionLabel)
instructionLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-45)
make.left.equalToSuperview().offset(20)
make.right.equalToSuperview().offset(-20)
}
}
private func updateUIForScanType() {
guard let scanTypes = scanTypes else {
gk_navTitle = YHScanType.all.title
instructionLabel.text = YHScanType.all.instructionText
return
}
gk_navTitle = scanTypes.title
instructionLabel.text = scanTypes.instructionText
}
}
// MARK: - Result Handling
extension YHScanViewController {
/// 默认结果处理(当没有设置回调时)
private func handleDefaultResult(_ result: YHScanResult) {
guard let type = result.recognizedType else {
showSuccessAndPop(message: "扫码成功")
return
}
if type == .checkIn {
handleCheckIn(code: result.code)
} else if type == .smartCabinet {
handleSmartCabinet(code: result.code)
}
}
// MARK: - 业务处理方法
private func handleCheckIn(code: String) {
printLog("签到扫码: \(code)")
// TODO: 调用签到 API
showSuccessAndPop(message: "签到成功")
}
private func handleSmartCabinet(code: String) {
printLog("智能柜扫码: \(code)")
// TODO: 调用智能柜开柜 API
showSuccessAndPop(message: "开柜成功")
}
// MARK: - Helper
private func showError(message: String) {
YHCommonAlertView.show("提示", message, "", "确定", fullGuestureEnable: false) {
} callBack: {
}
}
private func showSuccessAndPop(message: String) {
YHCommonAlertView.show("成功", message, "", "确定", fullGuestureEnable: false) {
} callBack: { [weak self] in
self?.navigationController?.popViewController(animated: true)
}
}
} }
// MARK: - 便捷初始化
extension YHScanViewController {
/// 便捷初始化方法
/// - Parameters:
/// - types: 扫码支持的类型(可以是单个或多个的组合)
/// - completion: 扫码成功回调
/// - Returns: 扫码控制器实例
static func create(types: YHScanType, completion: YHScanCompletion? = nil) -> YHScanViewController {
let vc = YHScanViewController()
vc.scanTypes = types
vc.scanCompletion = completion
return vc
}
}
// MARK: - 使用示例
/*
// 示例1: 单一类型 - 仅签到
let checkInVC = YHScanViewController.create(types: .checkIn) { result in
print("签到码: \(result.code)")
print("识别类型: \(result.recognizedType)")
}
navigationController?.pushViewController(checkInVC, animated: true)
// 示例2: 单一类型 - 仅智能柜
let cabinetVC = YHScanViewController.create(types: .smartCabinet) { result in
print("智能柜码: \(result.code)")
}
navigationController?.pushViewController(cabinetVC, animated: true)
// 示例3: 复合类型 - 同时支持签到和智能柜 ⭐
let multiVC = YHScanViewController.create(types: [.checkIn, .smartCabinet]) { result in
print("扫码内容: \(result.code)")
// 根据识别出的类型进行不同处理
if let type = result.recognizedType {
if type == .checkIn {
print("这是签到码")
APIManager.checkIn(code: result.code)
} else if type == .smartCabinet {
print("这是智能柜码")
APIManager.openCabinet(code: result.code)
}
}
}
navigationController?.pushViewController(multiVC, animated: true)
// 示例4: 所有类型
let allTypesVC = YHScanViewController.create(types: .all) { result in
// 自动识别并处理
}
navigationController?.pushViewController(allTypesVC, animated: true)
// 示例5: 二维码格式示例
// 签到码格式: "CHECKIN_xxxxx"
// 智能柜码格式: "CABINET_xxxxx"
// 或 JSON 格式: {"type": "checkin", "data": "xxxxx"}
// 示例6: 后续扩展新类型
/*
struct YHScanType: OptionSet {
let rawValue: Int
static let checkIn = YHScanType(rawValue: 1 << 0)
static let smartCabinet = YHScanType(rawValue: 1 << 1)
static let document = YHScanType(rawValue: 1 << 2) // ← 新增
static let payment = YHScanType(rawValue: 1 << 3) // ← 新增
}
// 使用组合
let vc = YHScanViewController.create(types: [.checkIn, .document]) { result in
// 同时支持签到和文档扫码
}
*/
*/
//
// YHScanModel.swift
// galaxy
//
// Created by alexzzw on 2025/10/14.
// Copyright © 2025 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
// MARK: - 扫码场景类型(支持复合类型)
struct YHScanType: OptionSet {
let rawValue: Int
static let checkIn = YHScanType(rawValue: 1 << 0) // 签到
static let smartCabinet = YHScanType(rawValue: 1 << 1) // 智能柜
// 预留扩展位
// static let document = YHScanType(rawValue: 1 << 2) // 文档
// static let payment = YHScanType(rawValue: 1 << 3) // 支付
/// 所有类型
static let all: YHScanType = [.checkIn, .smartCabinet]
var title: String {
if self == .checkIn {
return "扫码签到"
} else if self == .smartCabinet {
return "智能柜扫码"
} else if self == .all {
return "扫码"
} else {
return "多功能扫码"
}
}
var instructionText: String? {
if self == .checkIn {
return "将二维码放到框内"
} else if self == .smartCabinet {
return "将二维码放到框内"
} else if contains(.checkIn) && contains(.smartCabinet) {
return "将二维码放到框内"
} else {
return "将二维码放到框内"
}
}
/// 从二维码内容识别类型
func recognizeType(from code: String) -> YHScanType? {
// 根据二维码内容规则识别类型
if contains(.checkIn) && code.hasPrefix("CHECKIN_") {
return .checkIn
}
if contains(.smartCabinet) && code.hasPrefix("CABINET_") {
return .smartCabinet
}
// 可以添加更多识别规则
// 示例: JSON 格式识别
if let data = code.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let type = json["type"] as? String {
switch type {
case "checkin":
return contains(.checkIn) ? .checkIn : nil
case "cabinet":
return contains(.smartCabinet) ? .smartCabinet : nil
default:
break
}
}
// 如果只有一个类型,直接返回
if self == .checkIn {
return .checkIn
} else if self == .smartCabinet {
return .smartCabinet
}
return nil
}
}
// MARK: - 扫码结果回调
typealias YHScanCompletion = (YHScanResult) -> Void
struct YHScanResult {
let code: String
let recognizedType: YHScanType? // 识别出的具体类型
let supportedTypes: YHScanType // 支持的类型集合
let timestamp: Date
init(code: String, recognizedType: YHScanType?, supportedTypes: YHScanType) {
self.code = code
self.recognizedType = recognizedType
self.supportedTypes = supportedTypes
timestamp = Date()
}
}
...@@ -351,6 +351,7 @@ extension String { ...@@ -351,6 +351,7 @@ extension String {
func getPrivateUrl() -> String { func getPrivateUrl() -> String {
var replacedString = self.replacingOccurrences(of: "prod-bucket-v1.oss-cn-shenzhen.aliyuncs.com", with: "prod-cdn.galaxy-immi.com") var replacedString = self.replacingOccurrences(of: "prod-bucket-v1.oss-cn-shenzhen.aliyuncs.com", with: "prod-cdn.galaxy-immi.com")
replacedString = replacedString.replacingOccurrences(of: "test-bucket-v1.oss-cn-shenzhen.aliyuncs.com", with: "test-cdn.galaxy-immi.com") replacedString = replacedString.replacingOccurrences(of: "test-bucket-v1.oss-cn-shenzhen.aliyuncs.com", with: "test-cdn.galaxy-immi.com")
replacedString = replacedString.replacingOccurrences(of: "galaxy-filecenter.oss-cn-shenzhen.aliyuncs.com", with: "cdn-oss-galaxy-filecenter.galaxy-immi.com")
return replacedString return replacedString
} }
} }
...@@ -25,7 +25,6 @@ class LBXScanLineAnimation: UIImageView { ...@@ -25,7 +25,6 @@ class LBXScanLineAnimation: UIImageView {
if image != nil { if image != nil {
stepAnimation() stepAnimation()
} }
} }
@objc func stepAnimation() { @objc func stepAnimation() {
...@@ -35,37 +34,69 @@ class LBXScanLineAnimation: UIImageView { ...@@ -35,37 +34,69 @@ class LBXScanLineAnimation: UIImageView {
guard let image = self.image else { return } guard let image = self.image else { return }
// 重要:取消之前可能存在的延迟调用
NSObject.cancelPreviousPerformRequests(withTarget: self,
selector: #selector(LBXScanLineAnimation.stepAnimation),
object: nil)
var frame: CGRect = animationRect var frame: CGRect = animationRect
let hImg = image.size.height * animationRect.size.width / image.size.width let hImg = image.size.height * animationRect.size.width / image.size.width
frame.origin.y -= hImg // 重置到起始位置
frame.origin.y = animationRect.origin.y
frame.size.height = hImg frame.size.height = hImg
self.frame = frame self.frame = frame
self.alpha = 0.0 self.alpha = 0.0
UIView.animate(withDuration: 1.4, animations: { () in UIView.animate(withDuration: 1.4, animations: { [weak self] () in
guard let self = self else { return }
self.alpha = 1.0 self.alpha = 1.0
var frame = self.animationRect var frame = self.animationRect
let hImg = image.size.height * self.animationRect.size.width / image.size.width let hImg = image.size.height * self.animationRect.size.width / image.size.width
frame.origin.y += (frame.size.height - hImg) frame.origin.y = self.animationRect.origin.y + (self.animationRect.size.height - hImg)
frame.size.height = hImg frame.size.height = hImg
self.frame = frame self.frame = frame
}, completion: { (_: Bool) in }, completion: { [weak self] (_: Bool) in
guard let self = self else { return }
self.perform(#selector(LBXScanLineAnimation.stepAnimation), with: nil, afterDelay: 0.3)
// 检查动画是否应该继续
if self.isAnimationing {
self.perform(#selector(LBXScanLineAnimation.stepAnimation),
with: nil,
afterDelay: 0.3)
}
}) })
} }
func stopStepAnimating() { func stopStepAnimating() {
self.isHidden = true // 先设置标记为false,阻止新的动画开始
isAnimationing = false isAnimationing = false
// 取消所有延迟调用
NSObject.cancelPreviousPerformRequests(withTarget: self)
// 停止当前的动画
self.layer.removeAllAnimations()
// 隐藏视图
self.isHidden = true
// 重置frame到初始位置
if !animationRect.isEmpty {
var frame = animationRect
if let image = self.image {
let hImg = image.size.height * animationRect.size.width / image.size.width
frame.origin.y = animationRect.origin.y
frame.size.height = hImg
}
self.frame = frame
}
} }
static public func instance() -> LBXScanLineAnimation { static public func instance() -> LBXScanLineAnimation {
...@@ -73,7 +104,7 @@ class LBXScanLineAnimation: UIImageView { ...@@ -73,7 +104,7 @@ class LBXScanLineAnimation: UIImageView {
} }
deinit { deinit {
NSObject.cancelPreviousPerformRequests(withTarget: self)
stopStepAnimating() stopStepAnimating()
} }
} }
...@@ -38,39 +38,74 @@ class LBXScanNetAnimation: UIImageView { ...@@ -38,39 +38,74 @@ class LBXScanNetAnimation: UIImageView {
guard let image = self.image else { return } guard let image = self.image else { return }
// 重要:取消之前可能存在的延迟调用
NSObject.cancelPreviousPerformRequests(withTarget: self,
selector: #selector(LBXScanNetAnimation.stepAnimation),
object: nil)
var frame = animationRect var frame = animationRect
let hImg = image.size.height * animationRect.size.width / image.size.width let hImg = image.size.height * animationRect.size.width / image.size.width
frame.origin.y -= hImg // 重置到起始位置
frame.origin.y = animationRect.origin.y
frame.size.height = hImg frame.size.height = hImg
self.frame = frame self.frame = frame
self.alpha = 0.0 self.alpha = 0.0
UIView.animate(withDuration: 1.2, animations: { () in UIView.animate(withDuration: 1.2, animations: { [weak self] () in
guard let self = self else { return }
self.alpha = 1.0 self.alpha = 1.0
var frame = self.animationRect var frame = self.animationRect
let hImg = image.size.height * self.animationRect.size.width / image.size.width let hImg = image.size.height * self.animationRect.size.width / image.size.width
frame.origin.y += (frame.size.height - hImg) frame.origin.y = self.animationRect.origin.y + (self.animationRect.size.height - hImg)
frame.size.height = hImg frame.size.height = hImg
self.frame = frame self.frame = frame
}, completion: { (_: Bool) in }, completion: { [weak self] (_: Bool) in
guard let self = self else { return }
self.perform(#selector(LBXScanNetAnimation.stepAnimation), with: nil, afterDelay: 0.3)
// 检查动画是否应该继续
if self.isAnimationing {
self.perform(#selector(LBXScanNetAnimation.stepAnimation),
with: nil,
afterDelay: 0.3)
}
}) })
} }
func stopStepAnimating() { func stopStepAnimating() {
self.isHidden = true // 先设置标记为false,阻止新的动画开始
isAnimationing = false isAnimationing = false
// 取消所有延迟调用
NSObject.cancelPreviousPerformRequests(withTarget: self)
// 停止当前的动画
self.layer.removeAllAnimations()
// 隐藏视图
self.isHidden = true
// 重置frame到初始位置
if !animationRect.isEmpty {
var frame = animationRect
if let image = self.image {
let hImg = image.size.height * animationRect.size.width / image.size.width
frame.origin.y = animationRect.origin.y
frame.size.height = hImg
}
self.frame = frame
}
}
deinit {
NSObject.cancelPreviousPerformRequests(withTarget: self)
stopStepAnimating()
} }
} }
...@@ -80,6 +80,12 @@ class LBXScanViewController: YHBaseViewController, UIImagePickerControllerDelega ...@@ -80,6 +80,12 @@ class LBXScanViewController: YHBaseViewController, UIImagePickerControllerDelega
} }
} }
} }
override open func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NSObject.cancelPreviousPerformRequests(withTarget: self)
stopScan()
}
@objc open func startScan() { @objc open func startScan() {
if scanObj == nil { if scanObj == nil {
...@@ -161,11 +167,6 @@ class LBXScanViewController: YHBaseViewController, UIImagePickerControllerDelega ...@@ -161,11 +167,6 @@ class LBXScanViewController: YHBaseViewController, UIImagePickerControllerDelega
} }
} }
override open func viewWillDisappear(_ animated: Bool) {
NSObject.cancelPreviousPerformRequests(withTarget: self)
stopScan()
}
open func openPhotoAlbum() { open func openPhotoAlbum() {
LBXPermissions.authorizePhotoWith { [weak self] granted in LBXPermissions.authorizePhotoWith { [weak self] granted in
if granted { if granted {
......
...@@ -289,6 +289,8 @@ class YHOSSManager: NSObject, URLSessionDelegate { ...@@ -289,6 +289,8 @@ class YHOSSManager: NSObject, URLSessionDelegate {
return "prod-bucket-v1" return "prod-bucket-v1"
} else if url.contains("prod-cdn-pub.galaxy-immi.com") { } else if url.contains("prod-cdn-pub.galaxy-immi.com") {
return "prod-bucket-v1-pub" return "prod-bucket-v1-pub"
} else if url.contains("galaxy-filecenter.galaxy-immi.com") {
return "galaxy-filecenter"
} else { } else {
#if DEBUG #if DEBUG
printLog("OSS找不到正确的Bucket" ) printLog("OSS找不到正确的Bucket" )
......
#!/bin/sh
set -eu
# allow overriding the version
VERSION=${SENTRY_CLI_VERSION:-latest}
PLATFORM=`uname -s`
ARCH=`uname -m`
case "$PLATFORM" in
CYGWIN*) PLATFORM="Windows"
;;
MINGW*) PLATFORM="Windows"
;;
MSYS*) PLATFORM="Windows"
;;
Darwin) ARCH="universal"
;;
esac
case "$ARCH" in
armv6*) ARCH="armv7"
;;
armv7*) ARCH="armv7"
;;
armv8*) ARCH="aarch64"
;;
armv64*) ARCH="aarch64"
;;
aarch64*) ARCH="aarch64"
;;
esac
# If the install directory is not set, set it to a default
if [ -z ${INSTALL_DIR+x} ]; then
INSTALL_DIR=/usr/local/bin
fi
if [ -z ${INSTALL_PATH+x} ]; then
INSTALL_PATH="${INSTALL_DIR}/sentry-cli"
fi
DOWNLOAD_URL="https://release-registry.services.sentry.io/apps/sentry-cli/${VERSION}?response=download&arch=${ARCH}&platform=${PLATFORM}&package=sentry-cli"
echo "This script will automatically install sentry-cli (${VERSION}) for you."
echo "Installation path: ${INSTALL_PATH}"
if [ "x$(id -u)" = "x0" ]; then
echo "Warning: this script is currently running as root. This is dangerous. "
echo " Instead run it as normal user. We will sudo as needed."
fi
if [ -f "$INSTALL_PATH" ]; then
echo "error: sentry-cli is already installed."
echo " run \"sentry-cli update\" to update to latest version"
exit 1
fi
if ! hash curl 2> /dev/null; then
echo "error: you do not have 'curl' installed which is required for this script."
exit 1
fi
TEMP_FILE=`mktemp "${TMPDIR:-/tmp}/.sentrycli.XXXXXXXX"`
TEMP_HEADER_FILE=`mktemp "${TMPDIR:-/tmp}/.sentrycli-headers.XXXXXXXX"`
cleanup() {
rm -f "$TEMP_FILE"
rm -f "$TEMP_HEADER_FILE"
}
trap cleanup EXIT
HTTP_CODE=$(curl -SL --progress-bar "$DOWNLOAD_URL" -D "$TEMP_HEADER_FILE" --output "$TEMP_FILE" --write-out "%{http_code}")
if [ ${HTTP_CODE} -lt 200 ] || [ ${HTTP_CODE} -gt 299 ]; then
echo "error: your platform and architecture (${PLATFORM}-${ARCH}) is unsupported."
exit 1
fi
for PYTHON in python3 python2 python ''; do
if hash "$PYTHON"; then
break
fi
done
if [ "$PYTHON" ]; then
"$PYTHON" - <<EOF "${TEMP_FILE}" "${TEMP_HEADER_FILE}"
if 1:
import sys
import re
import hashlib
import binascii
validated = False
with open(sys.argv[2], "r") as f:
for line in f:
match = re.search("(?i)^digest:.?sha256=([^,\n ]+)", line)
if match is not None:
with open(sys.argv[1], "rb") as downloaded:
hasher = hashlib.sha256()
while True:
chunk = downloaded.read(4096)
if not chunk:
break
hasher.update(chunk)
calculated = hasher.digest()
expected = binascii.a2b_base64(match.group(1))
if calculated != expected:
print("error: checksum mismatch (got %s, expected %s)" % (
binascii.b2a_hex(calculated).decode("ascii"),
binascii.b2a_hex(expected).decode("ascii")
))
sys.exit(1)
validated = True
break
if not validated:
print("warning: unable to validate checksum because no checksum available")
EOF
else
echo "warning: python not available, unable to verify checksums"
fi
chmod 0755 "$TEMP_FILE"
if ! (mkdir -p "$(dirname "$INSTALL_PATH")" && mv "$TEMP_FILE" "$INSTALL_PATH") 2> /dev/null; then
sudo -k sh -c "mkdir -p \"$(dirname "$INSTALL_PATH")\" && mv \"$TEMP_FILE\" \"$INSTALL_PATH\""
fi
echo "Sucessfully installed $("$INSTALL_PATH" --version)"
VERSION=$("$INSTALL_PATH" --version | awk '{print $2}')
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
if (test -d "${HOME}/.oh-my-zsh") 2>/dev/null && [ $MAJOR -eq 2 ] && [ $MINOR -ge 22 ]; then
echo 'Detected Oh My Zsh, installing Zsh completions...'
if (mkdir -p "${HOME}/.oh-my-zsh/completions") 2>&1 && ("$INSTALL_PATH" completions zsh > "${HOME}/.oh-my-zsh/completions/_sentry_cli") 2>&1; then
echo "Successfully installed Zsh completions."
else
echo 'Warning: failed to install Zsh completions.'
fi
fi
echo 'Done!'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment