Browse Source

feat: 添加订阅商品购买

Destiny 8 tháng trước cách đây
mục cha
commit
b32820026e
18 tập tin đã thay đổi với 406 bổ sung111 xóa
  1. 8 4
      QuickSearchLocation.xcodeproj/project.pbxproj
  2. 1 0
      QuickSearchLocation/Classes/Common/Model/QSLGoodModel.swift
  3. 8 0
      QuickSearchLocation/Classes/Common/Model/QSLMemberModel.swift
  4. 30 0
      QuickSearchLocation/Classes/Common/Model/QSLSubscriptionModel.swift
  5. 14 0
      QuickSearchLocation/Classes/Network/QSLNetwork.swift
  6. 2 0
      QuickSearchLocation/Classes/Pages/QSLLogin/Controller/QSLLoginViewController.swift
  7. 30 1
      QuickSearchLocation/Classes/Pages/QSLMine/Controller/QSLMineController.swift
  8. 49 35
      QuickSearchLocation/Classes/Pages/QSLVip/Cell/QSLVipGoodCollectionViewCell.swift
  9. 137 1
      QuickSearchLocation/Classes/Pages/QSLVip/Controller/QSLVipController.swift
  10. 71 70
      QuickSearchLocation/Classes/Pages/QSLVip/QSLVipManager.swift
  11. 6 0
      QuickSearchLocation/Macro/QSLApi.swift
  12. 6 0
      QuickSearchLocation/Macro/QSLConfig.swift
  13. 22 0
      QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_avatars_icon.imageset/Contents.json
  14. BIN
      QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_avatars_icon.imageset/vip_avatars_icon@2x.png
  15. BIN
      QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_avatars_icon.imageset/vip_avatars_icon@3x.png
  16. 22 0
      QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_resume_btn.imageset/Contents.json
  17. BIN
      QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_resume_btn.imageset/vip_resume_btn@2x.png
  18. BIN
      QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_resume_btn.imageset/vip_resume_btn@3x.png

+ 8 - 4
QuickSearchLocation.xcodeproj/project.pbxproj

@@ -118,6 +118,7 @@
 		FE8360BD2D068DCB00978E03 /* QSLMemberModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE8360BC2D068DCB00978E03 /* QSLMemberModel.swift */; };
 		FE8360C02D06D33500978E03 /* QSLAppInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE8360BF2D06D33500978E03 /* QSLAppInfoController.swift */; };
 		FE94B4F42D23F09100D2B001 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE94B4F32D23F09100D2B001 /* LoadingViewController.swift */; };
+		FEC010E72D9E8B06008B8B0A /* QSLSubscriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC010E62D9E8AFE008B8B0A /* QSLSubscriptionModel.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -238,6 +239,7 @@
 		FE8360BC2D068DCB00978E03 /* QSLMemberModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QSLMemberModel.swift; sourceTree = "<group>"; };
 		FE8360BF2D06D33500978E03 /* QSLAppInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QSLAppInfoController.swift; sourceTree = "<group>"; };
 		FE94B4F32D23F09100D2B001 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
+		FEC010E62D9E8AFE008B8B0A /* QSLSubscriptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QSLSubscriptionModel.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -528,6 +530,7 @@
 		04F33BEC2BC78B4E003E2111 /* Model */ = {
 			isa = PBXGroup;
 			children = (
+				FEC010E62D9E8AFE008B8B0A /* QSLSubscriptionModel.swift */,
 				04F33BED2BC78B91003E2111 /* QSLUserModel.swift */,
 				04F33BEF2BC78D54003E2111 /* QSLMapTrackModel.swift */,
 				FE8360AA2D01930600978E03 /* QSLMessageModel.swift */,
@@ -1012,6 +1015,7 @@
 				FE8360A22D00814000978E03 /* QSLPopViewCell.swift in Sources */,
 				04B666BD2BC7D15A0020BFBD /* QSLHomeViewModel.swift in Sources */,
 				04F33BA62BC6367C003E2111 /* AppDelegate.swift in Sources */,
+				FEC010E72D9E8B06008B8B0A /* QSLSubscriptionModel.swift in Sources */,
 				FE94B4F42D23F09100D2B001 /* LoadingViewController.swift in Sources */,
 				04F33BCF2BC675C5003E2111 /* UIApplication+Extension.swift in Sources */,
 				04F33BF02BC78D54003E2111 /* QSLMapTrackModel.swift in Sources */,
@@ -1226,7 +1230,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 11;
+				CURRENT_PROJECT_VERSION = 12;
 				DEVELOPMENT_TEAM = Q364C8K9BL;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -1250,7 +1254,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0.1;
+				MARKETING_VERSION = 1.1;
 				PRODUCT_BUNDLE_IDENTIFIER = com.manbu.shouji;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -1270,7 +1274,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 11;
+				CURRENT_PROJECT_VERSION = 12;
 				DEVELOPMENT_TEAM = Q364C8K9BL;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -1294,7 +1298,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0.1;
+				MARKETING_VERSION = 1.1;
 				PRODUCT_BUNDLE_IDENTIFIER = com.manbu.shouji;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";

+ 1 - 0
QuickSearchLocation/Classes/Common/Model/QSLGoodModel.swift

@@ -38,5 +38,6 @@ struct QSLGoodModel: Modelable {
     
     mutating func mapping(_ json: JSON) {
         self.goodId = json["id"].intValue
+        self.content = json["description"].stringValue
     }
 }

+ 8 - 0
QuickSearchLocation/Classes/Common/Model/QSLMemberModel.swift

@@ -22,6 +22,14 @@ struct QSLMemberModel: Modelable {
     
     var permanent: Bool = false
     
+    var deviceId: String = ""
+    
+    var subscriptionGroup: String = ""
+    
+    var autoRenewStatus: Int = 0
+    
+    var subscriptionExpired: Bool?
+    
     mutating func mapping(_ json: JSON) {
               
     }

+ 30 - 0
QuickSearchLocation/Classes/Common/Model/QSLSubscriptionModel.swift

@@ -0,0 +1,30 @@
+//
+//  QSLSubscriptionModel.swift
+//  QuickSearchLocation
+//
+//  Created by Destiny on 2025/4/3.
+//
+
+import MoyaMapper
+import SwiftyJSON
+
+struct QSLSubscriptionModel: Modelable {
+    
+    var outTradeNo: String = ""
+    
+    var subscriptionGroup: String = ""
+    
+    var userId: String = ""
+    
+    var autoRenewStatus: Int = 0
+    
+    var renewItemId: Int = 0
+    
+    var expireTimestamp: Int = 0
+    
+    var expired: Bool = false
+    
+    mutating func mapping(_ json: JSON) {
+        
+    }
+}

+ 14 - 0
QuickSearchLocation/Classes/Network/QSLNetwork.swift

@@ -40,6 +40,8 @@ enum QSLNetworkAPI {
     case vipItemList(dict: [String: Any])
     case vipOrderSubmitAndPay(dict: [String: Any])
     case vipOrderPayStatus(dict: [String: Any])
+    case vipMemberResume(dict: [String: Any])
+    case vipCheckSubscription(dict: [String: Any])
     case contactList(dict: [String: Any])
     case contactCreate(dict: [String: Any])
     case contactFavor(dict: [String: Any])
@@ -83,6 +85,8 @@ extension QSLNetworkAPI: TargetType {
         case .vipItemList: return QSLApi.vip_item_list
         case .vipOrderSubmitAndPay: return QSLApi.vip_order_submitAndPay
         case .vipOrderPayStatus: return QSLApi.vip_order_payStatus
+        case .vipMemberResume: return QSLApi.vip_subscription_resume
+        case .vipCheckSubscription: return QSLApi.vip_subscription_check
         case .contactList: return QSLApi.contact_list
         case .contactCreate: return QSLApi.contact_create
         case .contactFavor: return QSLApi.contact_favor
@@ -188,6 +192,16 @@ extension QSLNetworkAPI: TargetType {
                 parameters[key] = value
             }
             break
+        case let .vipCheckSubscription(Dict):
+            for (key, value) in Dict {
+                parameters[key] = value
+            }
+            break
+        case let .vipMemberResume(Dict):
+            for (key, value) in Dict {
+                parameters[key] = value
+            }
+            break
         case let .contactList(Dict):
             for (key, value) in Dict {
                 parameters[key] = value

+ 2 - 0
QuickSearchLocation/Classes/Pages/QSLLogin/Controller/QSLLoginViewController.swift

@@ -299,6 +299,8 @@ extension QSLLoginViewController {
             gravityInstance?.track(QSLGravityConst.login_success)
             // 发送通知
             NotificationCenter.default.post(name: QSLNotification.QSLLogin, object: nil)
+            // 发送通知
+            NotificationCenter.default.post(name: QSLNotification.QSLRefreshMember, object: nil)
             
             // 返回
             self.backBtnAction()

+ 30 - 1
QuickSearchLocation/Classes/Pages/QSLMine/Controller/QSLMineController.swift

@@ -55,12 +55,41 @@ class QSLMineController: QSLBaseController {
         
         NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: QSLNotification.QSLLogout, object: nil)
         
-        NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: QSLNotification.QSLRefreshMember, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(requestVip), name: QSLNotification.QSLRefreshMember, object: nil)
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        self.requestVip()
     }
 }
 
 extension QSLMineController {
     
+    // 请求vip信息
+    @objc func requestVip() {
+        
+        QSLNetwork().request(.userMember(dict: [:])) { response in
+            
+            let memberModel = response.mapObject(QSLMemberModel.self, modelKey: "data")
+            QSLBaseManager.shared.userModel.memberModel = memberModel
+            
+            if memberModel.expired {
+                QSLBaseManager.shared.saveVipExpiredTime(time: 0)
+            } else {
+                QSLBaseManager.shared.saveVipExpiredTime(time: memberModel.endTimestamp)
+            }
+            
+            QSLBaseManager.shared.saveUserId(id: memberModel.userId)
+            
+            self.mineVipView.updateUI()
+//            NotificationCenter.default.post(name: QSLNotification.QSLRefreshMember, object: nil)
+            
+        } fail: { code, error in
+            
+        }
+    }
+    
     // 清除用户数据
     func requestClearUser() {
         

+ 49 - 35
QuickSearchLocation/Classes/Pages/QSLVip/Cell/QSLVipGoodCollectionViewCell.swift

@@ -39,14 +39,17 @@ class QSLVipGoodCollectionViewCell: UICollectionViewCell {
     lazy var goodDailyPriceLabel: UILabel = {
         
         let label = UILabel()
+        label.textAlignment = .center
+        label.addRadius(radius: 12.rpx)
+        label.backgroundColor = .hexStringColor(hexString: "#FFFEFC")
         let text = "0.35元/天"
         label.text(text)
-        label.font(13)
-        label.textColor = .hexStringColor(hexString: "#F12A1D")
-        if let range = text.range(of: "/") {
-            let nsRange = NSRange(text.startIndex..<range.lowerBound, in: text)
-            label.setRangeFontText(font: .textM(20), range: nsRange)
-        }
+        label.font(9)
+        label.textColor = .hexStringColor(hexString: "#818181")
+//        if let range = text.range(of: "/") {
+//            let nsRange = NSRange(text.startIndex..<range.lowerBound, in: text)
+//            label.setRangeFontText(font: .textM(20), range: nsRange)
+//        }
         return label
     }()
     
@@ -54,18 +57,19 @@ class QSLVipGoodCollectionViewCell: UICollectionViewCell {
        
         let label = UILabel()
         label.textAlignment = .center
-        label.addRadius(radius: 12.rpx)
-        label.backgroundColor = .hexStringColor(hexString: "#F6F6F6")
-        label.text("¥128")
-        label.font(12)
-        label.textColor = .hexStringColor(hexString: "#404040")
+//        label.addRadius(radius: 12.rpx)
+//        label.backgroundColor = .hexStringColor(hexString: "#F6F6F6")
+        label.text("¥128")
+        label.mediumFont(24)
+        label.textColor = .hexStringColor(hexString: "#F12A1D")
+        label.setSpecificTextColorFont("¥", color: .hexStringColor(hexString: "#F12A1D"), font: UIFont.systemFont(ofSize: 14, weight: .medium))
         return label
     }()
     
     lazy var originPriceLabel: UILabel = {
         
         let label = UILabel()
-        label.text("299")
+        label.text("¥299")
         label.font(13)
         label.textColor = .hexStringColor(hexString: "#A7A7A7")
         label.centerLineText(lineValue: 1, underlineColor: .hexStringColor(hexString: "#A7A7A7"))
@@ -96,30 +100,35 @@ class QSLVipGoodCollectionViewCell: UICollectionViewCell {
         self.tagIcon.isHidden = !model.popular
         
         self.goodNameLabel.text = model.name
+//        self.goodDailyPriceLabel.text = "0.001元/天"
         self.goodDailyPriceLabel.text = model.content
-        
-        if let range = model.content.range(of: "/") {
-            let nsRange = NSRange(model.content.startIndex..<range.lowerBound, in: model.content)
-            self.goodDailyPriceLabel.setRangeFontText(font: .textM(20), range: nsRange)
+        let width = model.content.singleLineWidth(font: .textF(10)) + 24.rpx
+        self.goodDailyPriceLabel.snp.updateConstraints { make in
+            make.width.equalTo(width)
         }
+//        if let range = model.content.range(of: "/") {
+//            let nsRange = NSRange(model.content.startIndex..<range.lowerBound, in: model.content)
+//            self.goodDailyPriceLabel.setRangeFontText(font: .textM(20), range: nsRange)
+//        }
 
         var priceText = ""
         if model.amount.truncatingRemainder(dividingBy: 100) == 0 {
-            priceText = "¥\(Int(model.amount / 100))"
+            priceText = "¥\(Int(model.amount / 100))"
         } else {
-            priceText = String(format: "%.2lf", model.amount / 100 )
+            priceText = String(format: "¥%.2lf", model.amount / 100 )
         }
         self.priceLabel.text = priceText
-        let width = priceText.singleLineWidth(font: .textF(12)) + 28.rpx
-        self.priceLabel.snp.updateConstraints { make in
-            make.width.equalTo(width)
-        }
+        self.priceLabel.setSpecificTextColorFont("¥", color: .hexStringColor(hexString: "#F12A1D"), font: UIFont.systemFont(ofSize: 14, weight: .medium))
+//        let width = priceText.singleLineWidth(font: .textF(12)) + 28.rpx
+//        self.priceLabel.snp.updateConstraints { make in
+//            make.width.equalTo(width)
+//        }
         
         var orinalPriceText = ""
         if model.originalAmount.truncatingRemainder(dividingBy: 100) == 0 {
-            orinalPriceText = "\(Int(model.originalAmount / 100))"
+            orinalPriceText = "¥\(Int(model.originalAmount / 100))"
         } else {
-            orinalPriceText = String(format: "%.2lf", model.originalAmount / 100 )
+            orinalPriceText = String(format: "¥%.2lf", model.originalAmount / 100 )
         }
         self.originPriceLabel.text = orinalPriceText
     }
@@ -149,25 +158,30 @@ extension QSLVipGoodCollectionViewCell {
             make.top.equalTo(16.rpx)
         }
         
-        self.bgView.addSubview(goodDailyPriceLabel)
-        goodDailyPriceLabel.snp.makeConstraints { make in
-            make.centerX.equalToSuperview()
-            make.top.equalTo(43.rpx)
-        }
-        
         self.bgView.addSubview(priceLabel)
-        let width = "¥128".singleLineWidth(font: .textF(12)) + 28.rpx
+//        let width = "¥128".singleLineWidth(font: .textF(12)) + 28.rpx
         priceLabel.snp.makeConstraints { make in
-            make.width.equalTo(width)
-            make.height.equalTo(21.rpx)
-            make.bottom.equalTo(-26.rpx)
+//            make.width.equalTo(width)
+//            make.height.equalTo(21.rpx)
+            make.top.equalTo(goodNameLabel.snp.bottom).offset(5.rpx)
+            make.top.equalTo(45.rpx)
             make.centerX.equalToSuperview()
         }
         
         self.bgView.addSubview(originPriceLabel)
         originPriceLabel.snp.makeConstraints { make in
             make.centerX.equalToSuperview()
-            make.bottom.equalTo(-6.rpx)
+            make.top.equalTo(priceLabel.snp.bottom).offset(5.rpx)
         }
+        
+        let width = "0.001元/天".singleLineWidth(font: .textF(10)) + 24.rpx
+        self.bgView.addSubview(goodDailyPriceLabel)
+        goodDailyPriceLabel.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.bottom.equalTo(-8.rpx)
+            make.height.equalTo(21.rpx)
+            make.width.equalTo(width)
+        }
+        
     }
 }

+ 137 - 1
QuickSearchLocation/Classes/Pages/QSLVip/Controller/QSLVipController.swift

@@ -48,6 +48,31 @@ class QSLVipController: QSLBaseController {
         return button
     }()
     
+    lazy var peopleCountView: UIView = {
+       
+        let view = UIView()
+        view.layer.cornerRadius = 12.rpx
+        view.layer.borderColor = UIColor.white.withAlphaComponent(0.16).cgColor
+        view.layer.borderWidth = 1.rpx
+        return view
+    }()
+    
+    lazy var peopleAvatarImageView: UIImageView = {
+       
+        let imageView = UIImageView()
+        imageView.image = UIImage(named: "vip_avatars_icon")
+        return imageView
+    }()
+    
+    lazy var peopleCountLabel: UILabel = {
+       
+        let label = UILabel()
+        label.text("1456488人 已开通VIP")
+        label.color("#FFE6C0")
+        label.font(12)
+        return label
+    }()
+    
     lazy var scrollView: UIScrollView = {
        
         let scrollView = UIScrollView()
@@ -103,6 +128,18 @@ class QSLVipController: QSLBaseController {
         return imageView
     }()
     
+    lazy var resumeBtn: UIButton = {
+       
+        let button = UIButton()
+        button.title("恢复购买")
+        button.setTitleColor(.hexStringColor(hexString: "#000000").withAlphaComponent(0.5), for: .normal)
+        button.titleLabel?.font = UIFont.systemFont(ofSize: 13, weight: .medium)
+        button.image(UIImage(named: "vip_resume_btn"))
+        button.setImageTitleLayout(.imgLeft, spacing: 2.rpx)
+        button.addTarget(self, action: #selector(resumeBtnAction), for: .touchUpInside)
+        return button
+    }()
+    
     lazy var goodsCollectionView: UICollectionView = {
             
         let layout = UICollectionViewFlowLayout()
@@ -278,6 +315,8 @@ class QSLVipController: QSLBaseController {
         
         requestItemList()
         
+        calculateBuyCount()
+        
         if let type = self.type {
             switch type {
             case .homeRoad:
@@ -318,6 +357,53 @@ extension QSLVipController {
         selectBtn.isSelected = !selectBtn.isSelected
     }
     
+    // 恢复按钮点击
+    @objc func resumeBtnAction() {
+        
+        let memberModel = QSLBaseManager.shared.userModel.memberModel
+        let expired = memberModel.expired
+        if !expired {
+            QSLLoading.success(text: "您已经在订阅中,无需恢复")
+            return
+        }
+        
+        QSLLoading.show()
+        QSLVipManager.shared.restoreAction { isSuccess in
+            QSLVipManager.shared.isPaying = false
+            if isSuccess {
+                
+                QSLNetwork().request(.userMember(dict: [:])) { response in
+                    
+                    let model = response.mapObject(QSLMemberModel.self, modelKey: "data")
+                    QSLBaseManager.shared.userModel.memberModel = model
+                    
+                    if model.expired {
+                        QSLBaseManager.shared.saveVipExpiredTime(time: 0)
+                    } else {
+                        QSLBaseManager.shared.saveVipExpiredTime(time: model.endTimestamp)
+                    }
+                    
+                    QSLBaseManager.shared.saveUserId(id: model.userId)
+                    
+                    if model.endTimestamp > memberModel.endTimestamp {
+                        QSLLoading.success(text: "恢复成功")
+                        // 支付成功通知
+                        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
+                            self.navigationController?.popViewController(animated: true)
+                        }
+                    } else {
+                        QSLLoading.error(text: "没有可供恢复的订阅")
+                    }
+                    
+                } fail: { code, error in
+                    QSLLoading.error(text: "没有可供恢复的订阅")
+                }
+            } else {
+                QSLLoading.error(text: "没有可供恢复的订阅")
+            }
+        }
+    }
+    
     @objc func unlockBtnAction() {
         
         switch self.selectGood?.level {
@@ -348,6 +434,13 @@ extension QSLVipController {
             return
         }
         
+        let memberModel = QSLBaseManager.shared.userModel.memberModel
+        
+        if let subscriptionExpired = memberModel.subscriptionExpired, !subscriptionExpired {
+            self.view.toast(text: "你已经订阅了")
+            return
+        }
+        
         if goodList.count > 0, let selectGood = self.selectGood {
             QSLLoading.show()
             QSLVipManager.shared.startPay(goods: selectGood) { status, outTradeNo in
@@ -431,9 +524,10 @@ extension QSLVipController {
 
 extension QSLVipController {
     
+    // 请求商品列表
     func requestItemList() {
         
-        QSLNetwork().request(.vipItemList(dict: [:])) { response in
+        QSLNetwork().request(.vipItemList(dict: ["itemListType": 1])) { response in
             let list = response.mapArray(QSLGoodModel.self, modelKey: "data>list")
             self.goodList = list
             if self.goodList.count > 0 {
@@ -445,6 +539,20 @@ extension QSLVipController {
             self.view.toast(text: "加载商品列表失败")
         }
     }
+    
+    // 计算购买人数
+    func calculateBuyCount() {
+        
+        var num = 145883
+        if let localNum = UserDefaults.standard.value(forKey: QSLConfig.user_default_local_buy_count) as? Int {
+            num = localNum
+        }
+        
+        let randomAddCount = Int.random(in: 1...10)
+        num = num + randomAddCount
+        UserDefaults.standard.setValue(num, forKey: QSLConfig.user_default_local_buy_count)
+        self.peopleCountLabel.text("\(num)人 已开通VIP")
+    }
 }
 
 // MARK: - 设置 CollectionView
@@ -561,6 +669,26 @@ extension QSLVipController {
             make.top.equalTo(QSLConst.qsl_kStatusBarFrameH)
         }
         
+        self.view.addSubview(peopleCountView)
+        peopleCountView.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 212.rpx, height: 24.rpx))
+            make.right.equalTo(-13.rpx)
+            make.centerY.equalTo(backButton.snp.centerY)
+        }
+        
+        peopleCountView.addSubview(peopleAvatarImageView)
+        peopleAvatarImageView.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 72.rpx, height: 16.6.rpx))
+            make.left.equalTo(3.rpx)
+            make.centerY.equalTo(peopleCountView.snp.centerY)
+        }
+        
+        peopleCountView.addSubview(peopleCountLabel)
+        peopleCountLabel.snp.makeConstraints { make in
+            make.left.equalTo(peopleAvatarImageView.snp.right).offset(7.rpx)
+            make.centerY.equalTo(peopleCountView.snp.centerY)
+        }
+        
         self.view.addSubview(scrollView)
         scrollView.snp.makeConstraints { make in
             make.left.right.equalTo(0)
@@ -600,6 +728,7 @@ extension QSLVipController {
         mainView.addSubview(goodsBgView)
         goodsBgView.snp.makeConstraints { make in
             make.top.left.right.equalTo(0)
+            make.width.equalTo(QSLConst.qsl_kScreenW)
             make.height.equalTo(255.rpx)
         }
         
@@ -610,6 +739,13 @@ extension QSLVipController {
             make.top.equalTo(16.rpx)
         }
         
+        goodsBgView.addSubview(resumeBtn)
+        resumeBtn.snp.makeConstraints { make in
+            make.size.equalTo(CGSize(width: 75.rpx, height: 16.rpx))
+            make.right.equalTo(-18.rpx)
+            make.centerY.equalTo(vipGoodsTitleIcon.snp.centerY)
+        }
+        
         goodsBgView.addSubview(goodsCollectionView)
         goodsCollectionView.snp.makeConstraints { make in
             make.left.equalTo(18.rpx)

+ 71 - 70
QuickSearchLocation/Classes/Pages/QSLVip/QSLVipManager.swift

@@ -86,38 +86,39 @@ extension QSLVipManager {
         self.requestOrderResult(receiptData: receiptData)
     }
     
-//    func restoreAction(complete: restoreComplete?) {
-//        
-//        if complete != nil {
-//            self.restoreCompleteClosure = complete
-//        }
-//        
-//        // 支付前查询自动续费的订单,将订单置为支付状态
-//        let transactions = SKPaymentQueue.default().transactions
-//        if transactions.count > 0 {
-//            for tran in transactions {
-//                if tran.transactionState == .purchased {
-//                    SKPaymentQueue.default().finishTransaction(tran)
-//                }
-//            }
-//        }
-//        
-//        self.isPaying = true
-//        self.isCheck = false
-//        
-//        print("开始恢复订阅")
-//        SKPaymentQueue.default().restoreCompletedTransactions()
-//        
-//        Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { timer in
-//            if let restoreCompleteClosure = self.restoreCompleteClosure {
-//                print("恢复订阅 15秒超时")
-//                restoreCompleteClosure(false)
-//            }
-//            self.restoreCompleteClosure = nil
-//            // 停止定时器并从运行循环中移除
-//            timer.invalidate()
-//        }
-//    }
+    // 恢复订阅
+    func restoreAction(complete: restoreComplete?) {
+        
+        if complete != nil {
+            self.restoreCompleteClosure = complete
+        }
+        
+        // 支付前查询自动续费的订单,将订单置为支付状态
+        let transactions = SKPaymentQueue.default().transactions
+        if transactions.count > 0 {
+            for tran in transactions {
+                if tran.transactionState == .purchased {
+                    SKPaymentQueue.default().finishTransaction(tran)
+                }
+            }
+        }
+        
+        self.isPaying = true
+        self.isCheck = false
+        
+        print("开始恢复订阅")
+        SKPaymentQueue.default().restoreCompletedTransactions()
+        
+        Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { timer in
+            if let restoreCompleteClosure = self.restoreCompleteClosure {
+                print("恢复订阅 15秒超时")
+                restoreCompleteClosure(false)
+            }
+            self.restoreCompleteClosure = nil
+            // 停止定时器并从运行循环中移除
+            timer.invalidate()
+        }
+    }
 }
 
 // 网络请求
@@ -207,30 +208,30 @@ extension QSLVipManager {
         }
     }
     
-//    func requestRestoreOrder(receiptData: String) {
-//        
-//        self.isRestoring = true
-//        
-//        var params: [String: Any] = [String: Any]()
-//        params["receiptData"] = receiptData
-//        params["payPlatform"] = 2
-//        params["payMethod"] = 3
-//        
-//        QSLNetwork().request(.memberResume(dict: params)) { response in
-//            
-//            self.isRestoring = false
-//            if let restoreCompleteClosure = self.restoreCompleteClosure {
-//                restoreCompleteClosure(true)
-//            }
-//            self.restoreCompleteClosure = nil
-//        } fail: { code, msg in
-//            
-//            if let restoreCompleteClosure = self.restoreCompleteClosure {
-//                restoreCompleteClosure(false)
-//            }
-//            self.restoreCompleteClosure = nil
-//        }
-//    }
+    func requestRestoreOrder(receiptData: String) {
+        
+        self.isRestoring = true
+        
+        var params: [String: Any] = [String: Any]()
+        params["receiptData"] = receiptData
+        params["payPlatform"] = 2
+        params["payMethod"] = 3
+        
+        QSLNetwork().request(.vipMemberResume(dict: params)) { response in
+            
+            self.isRestoring = false
+            if let restoreCompleteClosure = self.restoreCompleteClosure {
+                restoreCompleteClosure(true)
+            }
+            self.restoreCompleteClosure = nil
+        } fail: { code, msg in
+            
+            if let restoreCompleteClosure = self.restoreCompleteClosure {
+                restoreCompleteClosure(false)
+            }
+            self.restoreCompleteClosure = nil
+        }
+    }
 }
 
 extension QSLVipManager: SKPaymentTransactionObserver {
@@ -277,19 +278,19 @@ extension QSLVipManager: SKPaymentTransactionObserver {
         }
     }
     
-//    // 恢复订阅
-//    func restoreOrder(transaction: SKPaymentTransaction) {
-//        
-//        if isRestoring { return }
-//        
-//        if let receiptUrl = Bundle.main.appStoreReceiptURL {
-//            let receiptData = try? Data(contentsOf: receiptUrl)
-//            if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) {
-//                debugPrint("receiptString = \(String(describing: receiptString))")
-//                self.requestRestoreOrder(receiptData: receiptString)
-//            }
-//        }
-//    }
+    // 恢复订阅
+    func restoreOrder(transaction: SKPaymentTransaction) {
+        
+        if isRestoring { return }
+        
+        if let receiptUrl = Bundle.main.appStoreReceiptURL {
+            let receiptData = try? Data(contentsOf: receiptUrl)
+            if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) {
+                debugPrint("receiptString = \(String(describing: receiptString))")
+                self.requestRestoreOrder(receiptData: receiptString)
+            }
+        }
+    }
     
     // 缓存支付的结果
     func cacheOrder(isFinish: Bool) {
@@ -321,7 +322,7 @@ extension QSLVipManager: SKPaymentTransactionObserver {
                     print("正在购买中,商品已添加进列表")
                 case .restored:
                     print("恢复订阅")
-//                    self.restoreOrder(transaction: tran)
+                    self.restoreOrder(transaction: tran)
                     SKPaymentQueue.default().finishTransaction(tran)
                 case .failed:
                     print("商品支付失败")

+ 6 - 0
QuickSearchLocation/Macro/QSLApi.swift

@@ -169,6 +169,12 @@ extension QSLApi {
     
     // 查询支付结果
     static let vip_order_payStatus = "/s/v1/order/payStatus"
+    
+    // 恢复订阅
+    static let vip_subscription_resume = "/s/v1/subscription/resume"
+    
+    // 查询订阅状态
+    static let vip_subscription_check = "/s/v1/subscription/check"
 }
 
 // 定位模块

+ 6 - 0
QuickSearchLocation/Macro/QSLConfig.swift

@@ -19,3 +19,9 @@ struct QSLConfig {
     static let AppPrivacyAgreementLink = "https://doc.v8dashen.com/doc/e9b57a2813d8ed11"
     
 }
+
+extension QSLConfig {
+    
+    // 购买人数
+    static let user_default_local_buy_count = "UserDefaultLocalBuyCount"
+}

+ 22 - 0
QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_avatars_icon.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "vip_avatars_icon@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "vip_avatars_icon@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_avatars_icon.imageset/vip_avatars_icon@2x.png


BIN
QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_avatars_icon.imageset/vip_avatars_icon@3x.png


+ 22 - 0
QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_resume_btn.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "vip_resume_btn@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "vip_resume_btn@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_resume_btn.imageset/vip_resume_btn@2x.png


BIN
QuickSearchLocation/Resources/Assets.xcassets/Vip/vip_resume_btn.imageset/vip_resume_btn@3x.png