QSLVipTrialVC.swift 19 KB

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