// // QSLActivityVipVC.swift // QuickSearchLocation // // Created by Destiny on 2025/9/23. // import UIKit import GKCycleScrollView import YYText class QSLActivityVipVC: QSLBaseController { var goodList: [QSLGoodModel] = [QSLGoodModel]() var selectGood: QSLGoodModel? var currentCell: QSLVipMostGoodCell? var dismissHandler: ((Bool) -> Void)? var isCancel = false override func viewDidLoad() { super.viewDidLoad() gravityInstance?.track(QSLGravityConst.activity_vip_show, properties: [:]) self.initializeView() self.requestNetwork() self.updateServiceLabelText(showSubscribe: false) // if(QSLBaseManager.shared.isLogin()){ // self.naviResumeBtn.isHidden = false // }else{ // self.naviResumeBtn.isHidden = true // } } @objc func privacyAction() { let vc = QSLWebViewController() vc.webUrl = QSLConfig.AppPrivacyAgreementLink vc.title = "隐私政策" self.present(vc, animated: true) } @objc func serviceAction() { let vc = QSLWebViewController() vc.webUrl = QSLConfig.AppServiceAgreementLink vc.title = "服务协议" self.present(vc, animated: true) } @objc func subscibeAction() { let vc = QSLWebViewController() vc.webUrl = QSLConfig.AppSubscibeAgreementLink vc.title = "续订说明" self.present(vc, animated: true) } @objc func payBtnAction() { if let curGood = self.selectGood { QSLCountdownManager.shared.selectGood = curGood } switch self.selectGood?.level { case 100 : gravityInstance?.track(QSLGravityConst.activity_vip_click, properties: ["package_type": "day"]) break case 700: gravityInstance?.track(QSLGravityConst.activity_vip_click, properties: ["package_type": "weekly"]) break; case 3100: gravityInstance?.track(QSLGravityConst.activity_vip_click, properties: ["package_type": "monthly"]) break; case 9200: gravityInstance?.track(QSLGravityConst.activity_vip_click, properties: ["package_type": "quarterly"]) break; case 36600: gravityInstance?.track(QSLGravityConst.activity_vip_click, properties: ["package_type": "yearly"]) break; case 3660000: gravityInstance?.track(QSLGravityConst.activity_vip_click, properties: ["package_type": "lifetime"]) break; default: break; } 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.showWithCancel() QSLVipManager.shared.startPay(goods: selectGood) { [self] status, outTradeNo in QSLVipManager.shared.isPaying = false if status == .success { QSLLoading.success(text: "支付成功") //弹出是否好评的弹窗 //QSLGuideusersToCommentManager.commentShare.manageWhetherTriggerPopUpWindow(QSLGuideusersToCommentType.member) // 引力传递支付事件 if let selectGood = self.selectGood { gravityInstance?.trackPayEvent(withAmount: Int32(selectGood.amount), withPayType: "CNY", withOrderId: outTradeNo, withPayReason: selectGood.name, withPayMethod: "apple") } #if DEBUG #else //苹果广告奇异果传递支付事件 QSWikiHandle.shared.addEventResultAttribution(eventDict: ["event_name": "pay", "event_val": selectGood.amount]) #endif gravityInstance?.track(QSLGravityConst.activity_vip_result, properties: ["purchase_result": "success","pay_amount":Int32(selectGood.amount)]) DispatchQueue.main.asyncAfter(deadline: .now() + 3) { self.dismiss(animated: false) NotificationCenter.default.post(name: QSLNotification.QSLRefreshMember, object: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { if(!QSLBaseManager.shared.isLogin()){ QSLJumpManager.shared.pushToLogin(type: .member) } } } } else if status == .cancel { gravityInstance?.track(QSLGravityConst.activity_vip_result, properties: ["purchase_result": "cancel","pay_amount":Int32(selectGood.amount)]) QSLLoading.error(text: "支付取消") // payFailAlertTip() self.isCancel = true } else if status == .fail { gravityInstance?.track(QSLGravityConst.activity_vip_result, properties: ["purchase_result": "fail","pay_amount":Int32(selectGood.amount)]) gravityInstance?.track(QSLGravityConst.vip_fail) QSLLoading.error(text: "支付失败") // payFailAlertTip() self.isCancel = true } } } } func payFailAlertTip() { // gravityInstance?.track(QSLGravityConst.activity_vip_retention_show, properties: ["trigger_type":"cancel_payment"]) if let currentWindow = UIApplication.keyWindow { QSLRetainPopUpAlertView.alert(view: currentWindow, isOneBtn: true, oneBtnText: "继续支付", oneBtnClosure: { [weak self] in self?.payBtnAction() // gravityInstance?.track(QSLGravityConst.activity_vip_retention_click, properties: ["button":"continue_payment"]) },secondBtnClosure: { // gravityInstance?.track(QSLGravityConst.activity_vip_retention_click, properties: ["button":"close"]) }) } } @objc func requestNetwork() { // 定义并赋值 itemListDict(根据条件变化) let itemListDict: [String: Any] = ["itemListType": 2] // 网络请求时使用 itemListDict 作为参数(关键修复) QSLNetwork().request(.vipActivityItemList(dict: itemListDict)) { response in // 后续逻辑不变... let list = response.mapArray(QSLGoodModel.self, modelKey: "data>list") self.goodList = list if self.goodList.count > 0 { self.goodList[0].isSelect = true self.selectGood = self.goodList[0] var height = 140.rpx if self.goodList.count == 2 { height = 200.rpx } else { let remainingItems = self.goodList.count - 1 let remainingRows = (remainingItems + 1) / 2 height = Int(134.rpx + CGFloat(remainingRows) * 124.rpx) } self.goodsCollectionView.snp.updateConstraints { make in make.height.equalTo(height) } self.scrollView.contentSize = CGSize(width: 0.0, height: self.goodsCollectionView.qsl_y+CGFloat(height)+100+QSLConst.qsl_kTabbarBottom/2+12) QSLCountdownManager.shared.selectGood = self.goodList[0] QSLCountdownManager.shared.updateHandler1 = { [weak self] timeString in self?.countdownLabel.updateCountdownText(timeString) } QSLCountdownManager.shared.startCountdown() NotificationCenter.default.post( name: Notification.Name("QSLHomeUpdateCouponViewNoti"), object: nil, userInfo: ["showCoupon": true] ) } self.goodsCollectionView.reloadData() } fail: { code, error in self.view.toast(text: "加载商品列表失败") } } override func backBtnAction() { self.dismissHandler?(self.isCancel) super.backBtnAction() } // 恢复按钮点击 @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: "没有可供恢复的订阅") } } } // 更新服务条款文本内容 func updateServiceLabelText(showSubscribe: Bool) { let attr = NSMutableAttributedString() // 固定部分:购买即同意 let firstAttr = NSMutableAttributedString(string: "购买前请先阅读") firstAttr.font(11) firstAttr.color(UIColor.init(white: 0, alpha: 0.4)) attr.append(firstAttr) // 《隐私权政策》 let privacyHL = YYTextHighlight() let privacyStr = "隐私政策" let privacyText = NSMutableAttributedString(string: privacyStr) privacyText.font(11) privacyText.color(UIColor.init(white: 0, alpha: 0.7)) privacyText.yy_setTextHighlight(privacyHL, range: NSRange(location: 0, length: privacyStr.count)) privacyHL.tapAction = { [weak self] containerView, text, range, rect in self?.privacyAction() } let underline1 = YYTextDecoration(style: .single, width: 1, color: UIColor(white: 0, alpha: 0.7)) privacyText.yy_textUnderline = underline1 attr.append(privacyText) let blankAttr = NSMutableAttributedString(string: "&") blankAttr.color(UIColor.init(white: 0, alpha: 0.4)) blankAttr.font(11) attr.append(blankAttr) // 《用户协议》 let serviceHL = YYTextHighlight() let serviceStr = "服务条款" let serviceText = NSMutableAttributedString(string: serviceStr) serviceText.font(11) serviceText.color(UIColor.init(white: 0, alpha: 0.7)) serviceText.yy_setTextHighlight(serviceHL, range: NSRange(location: 0, length: serviceStr.count)) serviceHL.tapAction = { [weak self] containerView, text, range, rect in self?.serviceAction() } let underline = YYTextDecoration(style: .single, width: 1, color: UIColor(white: 0, alpha: 0.7)) serviceText.yy_textUnderline = underline attr.append(serviceText) // 根据条件决定是否添加《续订说明》相关内容 if showSubscribe { attr.append(blankAttr) let andAttr = NSMutableAttributedString(string: "和") andAttr.font(10) andAttr.color(.hexStringColor(hexString: "#A7A7A7")) attr.append(andAttr) attr.append(blankAttr) let subcribeHL = YYTextHighlight() let subcribeStr = "《续订说明》" let subcribeText = NSMutableAttributedString(string: subcribeStr) subcribeText.font(10) subcribeText.color(.hexStringColor(hexString: "#E7B983")) subcribeText.yy_setTextHighlight(subcribeHL, range: NSRange(location: 0, length: subcribeStr.count)) subcribeHL.tapAction = { [weak self] containerView, text, range, rect in self?.subscibeAction() } attr.append(subcribeText) } serviceLabel.attributedText = attr serviceLabel.textAlignment = .center } lazy var scrollView: UIScrollView = { let scrollView = UIScrollView() // scrollView.delegate = self scrollView.bounces = false scrollView.backgroundColor = UIColor.white scrollView.showsVerticalScrollIndicator = false scrollView.contentInsetAdjustmentBehavior = .never return scrollView }() lazy var vipNaviView: UIView = { let view = UIView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW, QSLConst.qsl_kScreenH)) view.addCorner(conrners: [.topLeft,.topRight], radius: 12.rpx) view.backgroundColor = UIColor.clear return view }() lazy var naviTitleIcon: UILabel = { let label = UILabel() label.text("超值优惠") label.textColor = .white label.mediumFont(17) label.textAlignment = .center return label }() lazy var naviResumeBtn: UIButton = { let button = UIButton() button.title("恢复购买") button.setTitleColor(.white, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 11, weight: .medium) button.image(UIImage(named: "vip_resume_btn")?.withTintColor(UIColor.white)) button.setImageTitleLayout(.imgLeft, spacing: 2.rpx) button.addTarget(self, action: #selector(resumeBtnAction), for: .touchUpInside) return button }() lazy var backButton: UIButton = { let button = UIButton() button.image(UIImage(named: "vip_pay_failure_close")) button.addTarget(self, action: #selector(backBtnAction), for: .touchUpInside) return button }() lazy var vipTopBg: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "vip_activity_top_bg") return imageView }() lazy var vipTopTitleView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "vip_activity_top_title") return imageView }() lazy var vipDiamondView: UIButton = { let button = UIButton() button.image(UIImage(named: "vip_activity_top_diamond")) button.title("会员限时福利") button.font(14) button.textColor(.white) button.setImageTitleLayout(.imgLeft, spacing: 4.rpx) button.isUserInteractionEnabled = false return button }() lazy var vipContentView: UIView = { let view = UIView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW, QSLConst.qsl_kScreenH)) view.addCorner(conrners: [.topLeft,.topRight], radius: 12.rpx) view.backgroundColor = UIColor.white return view }() lazy var addTitleIcon: UILabel = { let label = UILabel() label.text("功能介绍") label.textColor = .hexStringColor(hexString: "#202020") label.boldFont(15) return label }() lazy var addTitleIconBg: UIView = { let view = UIView(frame: CGRectMake(0, 0, 117.rpx, 8.rpx)) view.addRadius(radius: 4.rpx) // 创建渐变色层 let gradientLayer = CAGradientLayer() gradientLayer.colors = [ UIColor.hexStringColor(hexString: "#00DDAA",alpha: 1).cgColor, UIColor.hexStringColor(hexString: "#00DDAA",alpha: 0).cgColor ] gradientLayer.startPoint = CGPoint(x: 0, y: 0.4) // 左中 gradientLayer.endPoint = CGPoint(x: 1, y: 0.4) // 右中 gradientLayer.frame = view.bounds // 确保在布局变化时更新frame view.layer.insertSublayer(gradientLayer, at: 0) // 添加布局变化的监听 view.layoutIfNeeded() view.layoutSubviews() return view }() lazy var cycleScrollView: GKCycleScrollView = { let cycleScrollView = GKCycleScrollView() cycleScrollView.dataSource = self cycleScrollView.delegate = self cycleScrollView.isAutoScroll = true cycleScrollView.isInfiniteLoop = true cycleScrollView.isChangeAlpha = false cycleScrollView.leftRightMargin = 12 cycleScrollView.topBottomMargin = 12 cycleScrollView.pageControl = pageControl cycleScrollView.reloadData() return cycleScrollView }() lazy var pageControl: GKPageControl = { let pageControl = GKPageControl() pageControl.style = .sizeDot pageControl.dotHeight = 5.rpx pageControl.dotWidth = 5.rpx pageControl.dotMargin = 4.rpx pageControl.pageIndicatorTintColor = .hexStringColor(hexString: "#EEEEEE") pageControl.currentPageIndicatorTintColor = QSLColor.themeMainColor return pageControl }() lazy var goodsCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.minimumLineSpacing = 0 layout.scrollDirection = .vertical let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.backgroundColor = .white collectionView.dataSource = self collectionView.delegate = self collectionView.showsHorizontalScrollIndicator = false collectionView.bounces = false collectionView.register(cellClass: QSLActivityVipCell.self) return collectionView }() lazy var bottomView: UIView = { let view = UIView() view.backgroundColor = UIColor.white return view }() lazy var countdownView: UIView = { let view = UIView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW - 24.rpx, 47.rpx)) view.addCorner(conrners: [.topLeft,.topRight], radius: 30.rpx) view.backgroundColor = .hexStringColor(hexString: "#FFFED8") return view }() lazy var countdownLabel: QSLCountdownView = { let label = QSLCountdownView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW - 24.rpx, 17.rpx),type: 2) return label }() lazy var unlockBtn: UIButton = { let btn = UIButton() btn.gradientBackgroundColor(color1: .hexStringColor(hexString: "#0E5E61"), color2: .hexStringColor(hexString: "#00434E"), width: QSLConst.qsl_kScreenW - 24.rpx, height: 50.rpx, direction: .horizontal) btn.addRadius(radius: 25.rpx) btn.title("立即解锁") btn.textColor(.hexStringColor(hexString: "#FFF8EF")) btn.mediumFont(18) btn.addTarget(self, action: #selector(payBtnAction), for: .touchUpInside) return btn }() lazy var serviceLabel: YYLabel = { let label = YYLabel() label.textAlignment = .center return label }() } extension QSLActivityVipVC{ func initializeView() { self.view.addSubview(scrollView) scrollView.snp.makeConstraints { make in make.top.left.equalTo(0) make.width.equalTo(QSLConst.qsl_kScreenW) make.height.equalTo(QSLConst.qsl_kScreenH) } self.view.addSubview(vipNaviView) vipNaviView.snp.makeConstraints { make in make.top.left.right.equalTo(0) make.height.equalTo(QSLConst.qsl_kNavFrameH) } vipNaviView.addSubview(backButton) backButton.snp.makeConstraints { make in make.width.equalTo(44.rpx) make.height.equalTo(44.rpx) make.left.equalTo(10.rpx) make.top.equalTo(QSLConst.qsl_kStatusBarFrameH) } vipNaviView.addSubview(naviTitleIcon) naviTitleIcon.snp.makeConstraints { make in make.centerX.equalToSuperview() make.width.equalTo(100.rpx) make.height.equalTo(20.rpx) make.centerY.equalTo(backButton.snp.centerY) } vipNaviView.addSubview(naviResumeBtn) naviResumeBtn.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 70.rpx, height: 18.rpx)) make.right.equalTo(-8.rpx) make.centerY.equalTo(backButton.snp.centerY) } self.scrollView.addSubview(vipTopBg) vipTopBg.snp.makeConstraints { make in make.left.top.equalTo(0) make.width.equalTo(QSLConst.qsl_kScreenW) make.height.equalTo(185.rpx) } vipTopBg.addSubview(vipTopTitleView) vipTopTitleView.snp.makeConstraints { make in make.left.equalTo(16.rpx) make.bottom.equalTo(vipTopBg.snp.bottom).offset(-16.rpx) make.height.equalTo(22.rpx) make.width.equalTo(183.rpx) } vipTopBg.addSubview(vipDiamondView) vipDiamondView.snp.makeConstraints { make in make.left.equalTo(16.rpx) make.bottom.equalTo(vipTopTitleView.snp.top).offset(-6.rpx) make.height.equalTo(20.rpx) make.width.equalTo(105.rpx) } self.scrollView.addSubview(vipContentView) vipContentView.snp.makeConstraints { make in make.top.equalTo(185.rpx) make.left.equalTo(0) make.width.equalTo(QSLConst.qsl_kScreenW) make.height.equalTo(230.rpx) } vipContentView.addSubview(addTitleIconBg) addTitleIconBg.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 117.rpx, height: 8.rpx)) make.left.equalTo(16.rpx) make.top.equalTo(26.rpx) } vipContentView.addSubview(addTitleIcon) addTitleIcon.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 157.rpx, height: 26.rpx)) make.left.equalTo(20.rpx) make.top.equalTo(12.rpx) } vipContentView.addSubview(cycleScrollView) cycleScrollView.snp.makeConstraints { make in make.left.right.equalTo(0) make.height.equalTo(150.rpx) make.top.equalTo(50.rpx) } vipContentView.addSubview(pageControl) pageControl.snp.makeConstraints { make in make.size.equalTo(CGSize(width: 68.rpx, height: 4.rpx)) make.centerX.equalToSuperview() make.top.equalTo(cycleScrollView.snp.bottom).offset(17.rpx) } self.scrollView.addSubview(goodsCollectionView) goodsCollectionView.snp.makeConstraints { make in make.left.equalTo(0) make.height.equalTo(150.rpx) make.width.equalTo(QSLConst.qsl_kScreenW) make.top.equalTo(vipContentView.snp.bottom).offset(0) } self.view.addSubview(bottomView) bottomView.snp.makeConstraints { make in make.right.equalTo(-12.rpx) make.left.equalTo(12.rpx) make.height.equalTo(100.rpx) make.bottom.equalTo(-QSLConst.qsl_kTabbarBottom/2) } bottomView.addSubview(countdownView) countdownView.snp.makeConstraints { make in make.right.left.top.equalTo(0) make.height.equalTo(47.rpx) } countdownView.addSubview(countdownLabel) countdownLabel.snp.makeConstraints { make in make.left.right.equalTo(0) make.top.equalTo(5.rpx) make.height.equalTo(17.rpx) } bottomView.addSubview(unlockBtn) unlockBtn.snp.makeConstraints { make in make.right.left.equalTo(0) make.height.equalTo(50.rpx) make.top.equalTo(25.rpx) } bottomView.addSubview(serviceLabel) serviceLabel.snp.makeConstraints { make in make.right.left.equalTo(0) make.height.equalTo(17.rpx) make.bottom.equalTo(0) } } } extension QSLActivityVipVC : GKCycleScrollViewDelegate, GKCycleScrollViewDataSource{ func cycleScrollView(_ cycleScrollView: GKCycleScrollView!, cellForViewAt index: Int) -> GKCycleScrollViewCell! { if let cell = cycleScrollView.dequeueReusableCell() { cell.imageView.image = UIImage(named: "vip_activity_banner_\(index+1)") return cell } let cell = GKCycleScrollViewCell() cell.imageView.image = UIImage(named: "vip_activity_banner_\(index+1)") return cell } func numberOfCells(in cycleScrollView: GKCycleScrollView!) -> Int { return 3 } func sizeForCell(in cycleScrollView: GKCycleScrollView!) -> CGSize { return CGSizeMake(QSLConst.qsl_kScreenW - 90.rpx, 150.rpx); } } extension QSLActivityVipVC: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return goodList.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(cellType: QSLActivityVipCell.self, cellForRowAt: indexPath) let model = self.goodList[indexPath.row] if (indexPath.row == 0) { cell.config(model: model, type: .top) } else { if(self.goodList.count > 2){ cell.config(model: model, type: .small) }else{ cell.config(model: model, type: .big) } } return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { var list = [QSLGoodModel]() for index in 0.. CGSize { if (indexPath.row == 0) { return CGSize(width: QSLConst.qsl_kScreenW-32.rpx, height: 112.rpx) } else { if(self.goodList.count == 2){ return CGSize(width: QSLConst.qsl_kScreenW-32.rpx, height: 72.rpx) }else{ return CGSize(width: (QSLConst.qsl_kScreenW-50.rpx)/2.0, height: 112.rpx) } } } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 0, left: 16.rpx, bottom: 0, right: 16.rpx) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 10.rpx } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { if(self.goodList.count == 2){ return 10.rpx }else{ return 16.rpx } } }