QSLVipTrialVC.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. //
  2. // QSLVipTrialVC.swift
  3. // QuickSearchLocation
  4. //
  5. // Created by Destiny on 2025/10/23.
  6. //
  7. import UIKit
  8. import YYText
  9. import GKCycleScrollView
  10. enum QSLTrialVipJumpType: Int {
  11. case notiPush
  12. case shortcut
  13. }
  14. class QSLVipTrialVC: QSLBaseController {
  15. var type: QSLTrialVipJumpType?
  16. var selectGood: QSLGoodModel?
  17. var bannerList : [String] = []
  18. override func viewDidLoad() {
  19. super.viewDidLoad()
  20. QSEventHandle.eventPush(eventName: QSLGravityConst.activity_exposure_page, eventProps: ["page_type":"activity_page"])
  21. QSEventHandle.eventPush(eventName: QSLGravityConst.activity_exposure_page, eventProps: ["page_type":"coupon_popup"])
  22. QSLCountdownManager.shared.addDelegate(self)
  23. setUI()
  24. requestNetwork()
  25. }
  26. func startTimer(){
  27. // 开始首页商品倒计时
  28. QSLCountdownManager.shared.startCountdown(type: .trial, seconds: 900000) {[weak self] remainingSeconds in
  29. } finishCallback: {
  30. }
  31. }
  32. //倒计时
  33. //
  34. func requestNetwork() {
  35. // 网络请求时使用 itemListDict 作为参数(关键修复)
  36. QSLNetwork().request(.wakeupTrialList(dict: ["itemListType": 2])) {[weak self] response in
  37. // 后续逻辑不变...
  38. let list = response.mapArray(QSLGoodModel.self, modelKey: "data>list")
  39. if list.count > 0 {
  40. self?.selectGood = list[0]
  41. self?.desLabel.text = self?.selectGood?.content ?? ""
  42. if(QSLCountdownManager.shared.trialGood == nil){
  43. QSLCountdownManager.shared.trialGood = list[0]
  44. self?.startTimer()
  45. NotificationCenter.default.post(
  46. name: Notification.Name("QSLHomeUpdateCouponViewNoti"),
  47. object: nil,
  48. userInfo: ["showCoupon": true, "couponType":"1"]
  49. )
  50. }else{
  51. QSLCountdownManager.shared.trialGood = list[0]
  52. if(!QSLCountdownManager.shared.isCountdownActive(for: .trial)){
  53. self?.countdownLabel.updateCountdownText("00 : 00 : 00")
  54. }else{
  55. NotificationCenter.default.post(
  56. name: Notification.Name("QSLHomeUpdateCouponViewNoti"),
  57. object: nil,
  58. userInfo: ["showCoupon": true, "couponType":"1"]
  59. )
  60. }
  61. }
  62. }
  63. let bannerModel = response.mapObject(QSLWakeupBannerModel.self, modelKey: "data")
  64. self?.bannerList = bannerModel.banners
  65. if(bannerModel.banners.count > 0){
  66. self?.bgCenterView.isHidden = true
  67. self?.cycleScrollView.isHidden = false
  68. self?.cycleScrollView.reloadData()
  69. }else{
  70. self?.bgCenterView.isHidden = false
  71. self?.cycleScrollView.isHidden = true
  72. }
  73. } fail: { code, error in
  74. self.view.toast(text: "加载商品列表失败")
  75. self.bgCenterView.isHidden = false
  76. self.cycleScrollView.isHidden = true
  77. }
  78. }
  79. // 恢复按钮点击
  80. @objc func resumeBtnAction() {
  81. let memberModel = QSLBaseManager.shared.userModel.memberModel
  82. let expired = memberModel.expired
  83. if !expired {
  84. QSLLoading.success(text: "您已经在订阅中,无需恢复")
  85. return
  86. }
  87. QSLLoading.show()
  88. QSLVipManager.shared.restoreAction { isSuccess in
  89. QSLVipManager.shared.isPaying = false
  90. if isSuccess {
  91. QSLNetwork().request(.userMember(dict: [:])) { response in
  92. let model = response.mapObject(QSLMemberModel.self, modelKey: "data")
  93. QSLBaseManager.shared.userModel.memberModel = model
  94. if model.expired {
  95. QSLBaseManager.shared.saveVipExpiredTime(time: 0)
  96. } else {
  97. QSLBaseManager.shared.saveVipExpiredTime(time: model.endTimestamp)
  98. }
  99. QSLBaseManager.shared.saveUserId(id: model.userId)
  100. if model.endTimestamp > memberModel.endTimestamp {
  101. QSLLoading.success(text: "恢复成功")
  102. // 支付成功通知
  103. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
  104. self.navigationController?.popViewController(animated: true)
  105. }
  106. } else {
  107. QSLLoading.error(text: "没有可供恢复的订阅")
  108. }
  109. } fail: { code, error in
  110. QSLLoading.error(text: "没有可供恢复的订阅")
  111. }
  112. } else {
  113. QSLLoading.error(text: "没有可供恢复的订阅")
  114. }
  115. }
  116. }
  117. @objc func payBtnAction(){
  118. QSEventHandle.eventPush(eventName: QSLGravityConst.activity_page_click, eventProps: ["page_type":"activity_page"])
  119. if(!QSLBaseManager.shared.isLogin()){
  120. if(QSLBaseManager.shared.isVip()){
  121. QSLJumpManager.shared.pushToLogin(type: .member)
  122. return
  123. }
  124. if(QSLConfig.loginPayEnable){
  125. QSLJumpManager.shared.pushToLogin(type: .member)
  126. return
  127. }
  128. }
  129. let memberModel = QSLBaseManager.shared.userModel.memberModel
  130. if let subscriptionExpired = memberModel.subscriptionExpired, !subscriptionExpired {
  131. UIApplication.keyWindow?.toast(text: "你已经订阅了")
  132. return
  133. }
  134. guard let currentGood = selectGood else { return }
  135. QSLLoading.show()
  136. QSLVipManager.shared.startPay(goods: currentGood) { status, outTradeNo in
  137. QSLVipManager.shared.isPaying = false
  138. if status == .success {
  139. QSLLoading.success(text: "支付成功")
  140. // 引力传递支付事件
  141. gravityInstance?.trackPayEvent(withAmount: Int32(currentGood.amount), withPayType: "CNY", withOrderId: outTradeNo, withPayReason: currentGood.name, withPayMethod: "apple")
  142. QSEventHandle.eventPush(eventName: QSLGravityConst.activity_purchase_result, eventProps: ["is_member":QSLBaseManager.shared.isVip(),"purchase_result": "success","pay_amount":Int32(currentGood.amount),"page_type":"activity_page"])
  143. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  144. NotificationCenter.default.post(name: QSLNotification.QSLRefreshMember, object: nil)
  145. self.backBtnAction()
  146. if(!QSLBaseManager.shared.isLogin()){
  147. QSLJumpManager.shared.pushToLogin(type: .member)
  148. }
  149. }
  150. } else if status == .cancel {
  151. QSEventHandle.eventPush(eventName: QSLGravityConst.activity_purchase_result, eventProps: ["is_member":QSLBaseManager.shared.isVip(),"purchase_result": "cancel","pay_amount":Int32(currentGood.amount),"page_type":"activity_page"])
  152. QSLLoading.error(text: "支付取消")
  153. } else if status == .fail {
  154. QSEventHandle.eventPush(eventName: QSLGravityConst.activity_purchase_result, eventProps: ["is_member":QSLBaseManager.shared.isVip(),"purchase_result": "fail","pay_amount":Int32(currentGood.amount),"page_type":"activity_page"])
  155. QSLLoading.error(text: "支付失败")
  156. }
  157. }
  158. }
  159. @objc func privacyAction() {
  160. let vc = QSLWebViewController()
  161. vc.webUrl = QSLConfig.AppPrivacyAgreementLink
  162. vc.title = "隐私政策"
  163. self.present(vc, animated: true)
  164. }
  165. @objc func serviceAction() {
  166. let vc = QSLWebViewController()
  167. vc.webUrl = QSLConfig.AppServiceAgreementLink
  168. vc.title = "服务协议"
  169. self.present(vc, animated: true)
  170. }
  171. func setUI(){
  172. self.view.addSubview(bottomSpaceView)
  173. bottomSpaceView.snp.makeConstraints { make in
  174. make.left.right.bottom.equalTo(0)
  175. make.height.equalTo(QSLConst.qsl_kTabbarBottom/2+1)
  176. }
  177. self.view.addSubview(bottomBgView)
  178. bottomBgView.snp.makeConstraints { make in
  179. make.left.right.equalTo(0)
  180. make.bottom.equalTo(bottomSpaceView.snp.top).offset(0)
  181. make.height.equalTo(QSLConst.qsl_kScreenW/1.4)
  182. }
  183. self.view.addSubview(bgImageView)
  184. bgImageView.snp.makeConstraints { make in
  185. make.left.top.right.equalTo(0)
  186. make.bottom.equalTo(bottomBgView.snp.top).offset(50.rpx)
  187. }
  188. bgImageView.addSubview(bgTopTit1View)
  189. bgTopTit1View.snp.makeConstraints { make in
  190. make.centerX.equalTo(bgImageView.snp.centerX)
  191. make.top.equalTo(QSLConst.qsl_kNavFrameH+20.rpx)
  192. }
  193. bgImageView.addSubview(bgTopTit2View)
  194. bgTopTit2View.snp.makeConstraints { make in
  195. make.centerX.equalTo(bgImageView.snp.centerX)
  196. make.top.equalTo(bgTopTit1View.snp.bottom).offset(1)
  197. }
  198. bgImageView.addSubview(desLabel)
  199. desLabel.snp.makeConstraints { make in
  200. make.right.left.equalTo(0)
  201. make.height.equalTo(17.rpx)
  202. make.top.equalTo(bgTopTit2View.snp.bottom).offset(6.rpx)
  203. }
  204. bgCenterView.isHidden = true
  205. bgImageView.addSubview(bgCenterView)
  206. bgCenterView.snp.makeConstraints { make in
  207. make.centerX.equalTo(bgImageView.snp.centerX)
  208. make.bottom.equalTo(0)
  209. }
  210. bgImageView.addSubview(cycleScrollView)
  211. cycleScrollView.snp.makeConstraints { make in
  212. make.centerX.equalTo(bgImageView.snp.centerX)
  213. make.width.equalTo(290.rpx)
  214. make.height.equalTo(344.rpx)
  215. make.bottom.equalTo(30.rpx)
  216. }
  217. self.view.addSubview(vipNaviView)
  218. vipNaviView.snp.makeConstraints { make in
  219. make.top.left.right.equalTo(0)
  220. make.height.equalTo(QSLConst.qsl_kNavFrameH)
  221. }
  222. vipNaviView.addSubview(backButton)
  223. backButton.snp.makeConstraints { make in
  224. make.width.equalTo(44.rpx)
  225. make.height.equalTo(44.rpx)
  226. make.left.equalTo(10.rpx)
  227. make.top.equalTo(QSLConst.qsl_kStatusBarFrameH)
  228. }
  229. vipNaviView.addSubview(naviTitleIcon)
  230. naviTitleIcon.snp.makeConstraints { make in
  231. make.centerX.equalToSuperview()
  232. make.width.equalTo(100.rpx)
  233. make.height.equalTo(20.rpx)
  234. make.centerY.equalTo(backButton.snp.centerY)
  235. }
  236. vipNaviView.addSubview(naviResumeBtn)
  237. naviResumeBtn.snp.makeConstraints { make in
  238. make.size.equalTo(CGSize(width: 70.rpx, height: 18.rpx))
  239. make.right.equalTo(-8.rpx)
  240. make.centerY.equalTo(backButton.snp.centerY)
  241. }
  242. self.bottomBgView.addSubview(bottomTitleView)
  243. bottomTitleView.snp.makeConstraints { make in
  244. make.top.equalTo(50.rpx)
  245. make.left.equalTo(32.rpx)
  246. make.right.equalTo(-32.rpx)
  247. }
  248. self.bottomBgView.addSubview(bottomBtnView)
  249. bottomBtnView.snp.makeConstraints { make in
  250. make.top.equalTo(bottomTitleView.snp.bottom).offset(20.rpx)
  251. make.left.right.bottom.equalTo(0)
  252. }
  253. bottomBtnView.addSubview(countdownView)
  254. countdownView.snp.makeConstraints { make in
  255. make.top.equalTo(0)
  256. make.left.equalTo(12.rpx)
  257. make.right.equalTo(-12.rpx)
  258. make.height.equalTo(47.rpx)
  259. }
  260. countdownView.addSubview(countdownLabel)
  261. countdownLabel.snp.makeConstraints { make in
  262. make.left.right.equalTo(0)
  263. make.top.equalTo(5.rpx)
  264. make.height.equalTo(17.rpx)
  265. }
  266. bottomBtnView.addSubview(unlockBtn)
  267. unlockBtn.snp.makeConstraints { make in
  268. make.left.equalTo(12.rpx)
  269. make.right.equalTo(-12.rpx)
  270. make.height.equalTo(50.rpx)
  271. make.top.equalTo(25.rpx)
  272. }
  273. bottomBtnView.addSubview(serviceLabel)
  274. serviceLabel.snp.makeConstraints { make in
  275. make.right.left.equalTo(0)
  276. make.height.equalTo(17.rpx)
  277. make.top.equalTo(unlockBtn.snp.bottom).offset(8.rpx)
  278. }
  279. updateServiceLabelText()
  280. self.view.bringSubviewToFront(bottomBgView)
  281. }
  282. // 更新服务条款文本内容
  283. func updateServiceLabelText() {
  284. let attr = NSMutableAttributedString()
  285. // 固定部分:购买即同意
  286. let firstAttr = NSMutableAttributedString(string: "购买前请先阅读")
  287. firstAttr.font(11)
  288. firstAttr.color(UIColor.init(white: 0, alpha: 0.4))
  289. attr.append(firstAttr)
  290. // 《隐私权政策》
  291. let privacyHL = YYTextHighlight()
  292. let privacyStr = "隐私政策"
  293. let privacyText = NSMutableAttributedString(string: privacyStr)
  294. privacyText.font(11)
  295. privacyText.color(UIColor.init(white: 0, alpha: 0.7))
  296. privacyText.yy_setTextHighlight(privacyHL, range: NSRange(location: 0, length: privacyStr.count))
  297. privacyHL.tapAction = { [weak self] containerView, text, range, rect in
  298. self?.privacyAction()
  299. }
  300. let underline1 = YYTextDecoration(style: .single, width: 1, color: UIColor(white: 0, alpha: 0.7))
  301. privacyText.yy_textUnderline = underline1
  302. attr.append(privacyText)
  303. let blankAttr = NSMutableAttributedString(string: "&")
  304. blankAttr.color(UIColor.init(white: 0, alpha: 0.4))
  305. blankAttr.font(11)
  306. attr.append(blankAttr)
  307. // 《用户协议》
  308. let serviceHL = YYTextHighlight()
  309. let serviceStr = "服务条款"
  310. let serviceText = NSMutableAttributedString(string: serviceStr)
  311. serviceText.font(11)
  312. serviceText.color(UIColor.init(white: 0, alpha: 0.7))
  313. serviceText.yy_setTextHighlight(serviceHL, range: NSRange(location: 0, length: serviceStr.count))
  314. serviceHL.tapAction = { [weak self] containerView, text, range, rect in
  315. self?.serviceAction()
  316. }
  317. let underline = YYTextDecoration(style: .single, width: 1, color: UIColor(white: 0, alpha: 0.7))
  318. serviceText.yy_textUnderline = underline
  319. attr.append(serviceText)
  320. serviceLabel.attributedText = attr
  321. serviceLabel.textAlignment = .center
  322. }
  323. lazy var vipNaviView: UIView = {
  324. let view = UIView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW, QSLConst.qsl_kScreenH))
  325. view.addCorner(conrners: [.topLeft,.topRight], radius: 12.rpx)
  326. view.backgroundColor = UIColor.clear
  327. return view
  328. }()
  329. lazy var naviTitleIcon: UILabel = {
  330. let label = UILabel()
  331. label.text("超值福利")
  332. label.textColor = .white
  333. label.mediumFont(17)
  334. label.textAlignment = .center
  335. return label
  336. }()
  337. lazy var naviResumeBtn: UIButton = {
  338. let button = UIButton()
  339. button.title("恢复订阅")
  340. button.setTitleColor(.white, for: .normal)
  341. button.titleLabel?.font = UIFont.systemFont(ofSize: 11, weight: .medium)
  342. button.image(UIImage(named: "vip_resume_btn")?.withTintColor(UIColor.white))
  343. button.setImageTitleLayout(.imgLeft, spacing: 2.rpx)
  344. button.addTarget(self, action: #selector(resumeBtnAction), for: .touchUpInside)
  345. return button
  346. }()
  347. lazy var backButton: UIButton = {
  348. let button = UIButton()
  349. button.image(UIImage(named: "vip_pay_failure_close"))
  350. button.addTarget(self, action: #selector(backBtnAction), for: .touchUpInside)
  351. return button
  352. }()
  353. lazy var bgImageView: UIImageView = {
  354. let item = UIImageView()
  355. item.contentMode = .scaleAspectFill
  356. item.image = UIImage(named: "vip_trial_top")
  357. item.layer.masksToBounds = true
  358. return item
  359. }()
  360. lazy var bgTopTit1View: UIImageView = {
  361. let item = UIImageView()
  362. item.contentMode = .scaleAspectFit
  363. item.image = UIImage(named: "vip_trial_top_title_1")
  364. item.layer.masksToBounds = true
  365. return item
  366. }()
  367. lazy var bgTopTit2View: UIImageView = {
  368. let item = UIImageView()
  369. item.contentMode = .scaleAspectFit
  370. item.image = UIImage(named: "vip_trial_top_title_2")
  371. item.layer.masksToBounds = true
  372. return item
  373. }()
  374. lazy var bgCenterView: UIImageView = {
  375. let item = UIImageView()
  376. item.contentMode = .scaleAspectFit
  377. item.image = UIImage(named: "vip_trial_center")
  378. item.layer.masksToBounds = true
  379. return item
  380. }()
  381. lazy var bottomSpaceView: UIView = {
  382. let item = UIView()
  383. item.backgroundColor = .white
  384. return item
  385. }()
  386. lazy var bottomBgView: UIImageView = {
  387. let item = UIImageView()
  388. item.contentMode = .scaleAspectFill
  389. item.image = UIImage(named: "vip_trial_bottom_bg")
  390. item.layer.masksToBounds = true
  391. item.isUserInteractionEnabled = true
  392. return item
  393. }()
  394. lazy var bottomTitleView: UIImageView = {
  395. let item = UIImageView()
  396. item.image = UIImage(named: "vip_trial_bottom_title")
  397. return item
  398. }()
  399. lazy var bottomBtnView: UIView = {
  400. let item = UIView()
  401. return item
  402. }()
  403. lazy var bottomView: UIView = {
  404. let view = UIView()
  405. view.backgroundColor = UIColor.white
  406. return view
  407. }()
  408. lazy var countdownView: UIView = {
  409. let view = UIView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW - 24.rpx, 47.rpx))
  410. view.addCorner(conrners: [.topLeft,.topRight], radius: 30.rpx)
  411. view.backgroundColor = .hexStringColor(hexString: "#FFFED8")
  412. return view
  413. }()
  414. lazy var countdownLabel: QSLCountdownView = {
  415. let label = QSLCountdownView(frame: CGRectMake(0, 0, QSLConst.qsl_kScreenW - 24.rpx, 17.rpx),type: 2)
  416. return label
  417. }()
  418. lazy var unlockBtn: UIButton = {
  419. let btn = UIButton()
  420. btn.gradientBackgroundColor(color1: .hexStringColor(hexString: "#0E5E61"), color2: .hexStringColor(hexString: "#00434E"), width: QSLConst.qsl_kScreenW - 24.rpx, height: 50.rpx, direction: .horizontal)
  421. btn.addRadius(radius: 25.rpx)
  422. btn.title("立即领取")
  423. btn.textColor(.hexStringColor(hexString: "#FFF8EF"))
  424. btn.mediumFont(18)
  425. btn.addTarget(self, action: #selector(payBtnAction), for: .touchUpInside)
  426. return btn
  427. }()
  428. lazy var serviceLabel: YYLabel = {
  429. let label = YYLabel()
  430. label.textAlignment = .center
  431. return label
  432. }()
  433. lazy var desLabel: YYLabel = {
  434. let label = YYLabel()
  435. label.textAlignment = .center
  436. label.textColor = UIColor.init(white: 1, alpha: 0.4)
  437. label.font = UIFont.systemFont(ofSize: 12)
  438. return label
  439. }()
  440. lazy var cycleScrollView: GKCycleScrollView = {
  441. let cycleScrollView = GKCycleScrollView()
  442. cycleScrollView.dataSource = self
  443. cycleScrollView.delegate = self
  444. cycleScrollView.isAutoScroll = true
  445. cycleScrollView.isInfiniteLoop = true
  446. cycleScrollView.isChangeAlpha = false
  447. cycleScrollView.leftRightMargin = 0
  448. cycleScrollView.topBottomMargin = 0
  449. return cycleScrollView
  450. }()
  451. }
  452. extension QSLVipTrialVC : GKCycleScrollViewDelegate, GKCycleScrollViewDataSource{
  453. func cycleScrollView(_ cycleScrollView: GKCycleScrollView!, cellForViewAt index: Int) -> GKCycleScrollViewCell! {
  454. if let cell = cycleScrollView.dequeueReusableCell() {
  455. cell.imageView.kf.setImage(with: URL(string: self.bannerList[index]))
  456. cell.layer.masksToBounds = true
  457. cell.contentMode = .scaleAspectFit
  458. return cell
  459. }
  460. let cell = GKCycleScrollViewCell()
  461. cell.imageView.kf.setImage(with: URL(string: self.bannerList[index]))
  462. cell.layer.masksToBounds = true
  463. cell.contentMode = .scaleAspectFit
  464. return cell
  465. }
  466. func numberOfCells(in cycleScrollView: GKCycleScrollView!) -> Int {
  467. return self.bannerList.count
  468. }
  469. func sizeForCell(in cycleScrollView: GKCycleScrollView!) -> CGSize {
  470. return CGSizeMake(290.rpx, 344.rpx);
  471. }
  472. }
  473. extension QSLVipTrialVC : QSLCountdownManagerDelegate{
  474. func countdownManager(_ manager: QSLCountdownManager, didUpdateCountdown type: QSLCountdownType, remainingSeconds: Int) {
  475. // 处理倒计时更新
  476. if type == .trial {
  477. let totalSeconds = remainingSeconds / 1000
  478. let minutes = totalSeconds / 60
  479. let seconds = totalSeconds % 60
  480. var milliseconds = remainingSeconds % 1000
  481. if(milliseconds <= 1){
  482. milliseconds = 0
  483. }
  484. // 格式:MM:SS:SSS(例如 "14:59:500")
  485. let timeString = String(format: "%02d : %02d : %03d", minutes, seconds, milliseconds)
  486. self.countdownLabel.updateCountdownText(timeString)
  487. }
  488. }
  489. func countdownManager(_ manager: QSLCountdownManager, didFinishCountdown type: QSLCountdownType) {
  490. }
  491. }