// // PromotionalActivitiesView.swift // QuickLocation // // Created by 八条 on 2026/7/3. // import UIKit import RxSwift import RxCocoa enum PromotionalActivitiesItem { case recommend(model: VipExpenseModel) case normal(model: VipExpenseModel) } class PromotionalActivitiesView: UIView { var disposeBag = DisposeBag() private func setupRx() { backBtn.rx.tap.subscribe(onNext: { _ in VipWaivePopView.show() }).disposed(by: disposeBag) } private func setupUI() { addSubview(scrollView) addSubview(navBarView) navBarView.addSubview(navTitleLabel) addSubview(backBtn) navBarView.layoutChain .edges(excludingEdge: .bottom) .height(kNaviHeight) navTitleLabel.layoutChain .top(kStatusBarHeight + 12) .centerY(backBtn) .centerX() backBtn.layoutChain .top(kStatusBarHeight + 12) .left(15) .width(24) .height(24) addSubview(bottomView) bottomView.layoutChain .edges(excludingEdge: .top) .height(120) scrollView.layoutChain .edges(excludingEdge: .bottom) .bottomToTopOfView(bottomView) } lazy var navBarView: UIView = { let view = UIView() view.backgroundColor = .white view.alpha = 0 return view }() lazy var navTitleLabel: UILabel = { let label = UILabel() label.text = "超值优惠" label.font = .systemFont(ofSize: 18, weight: .medium) label.textColor = ThemeManager.shared.color.titleAuxColor label.textAlignment = .center return label }() lazy var backBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Common/back"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 15, right: 100) return btn }() lazy var scrollView: UIScrollView = { let view = UIScrollView() view.backgroundColor = UIColor(hexStr: "#F6F9FB") view.showsVerticalScrollIndicator = false view.delegate = self view.bounces = false view.contentInsetAdjustmentBehavior = .never view.addSubview(scrollContentView) scrollContentView.layoutChain .edges().widthToView(view) let headerBgImgView = UIImageView(image: UIImage(named: "PromotionalActivities/header_bg")) headerBgImgView.contentMode = .scaleAspectFill scrollContentView.addSubview(headerBgImgView) headerBgImgView.layoutChain .edges(excludingEdge: .bottom) .heightToWidth(285/375) let infoView = UIView() infoView.backgroundColor = UIColor(hexStr: "#F6F9FB") infoView.layer.cornerRadius = 30 scrollContentView.addSubview(infoView) infoView.layoutChain .topToBottomOfView(headerBgImgView, offset: -65) .edges(excludingEdge: .top) let iconView = UIImageView(image: UIImage(named: "PromotionalActivities/vip_icon")) infoView.addSubview(iconView) iconView.layoutChain .top(20) .left(15) .width(20) .heightToWidth(1) let titleLab = UILabel() titleLab.text = "即刻加入会员" titleLab.font = .systemFont(ofSize: 16, weight: .semibold) titleLab.textColor = UIColor(hexStr: "#333333") infoView.addSubview(titleLab) titleLab.layoutChain .leftToRightOfView(iconView, offset: 4) .centerY(iconView) scrollContentView.addSubview(tableView) tableView.layoutChain .topToBottomOfView(titleLab, offset: 0) .edgesHorzontal() let agreementView = UIView() infoView.addSubview(agreementView) agreementView.layoutChain .topToBottomOfView(tableView, offset: 15) .left(15) .right(15) .height(16) agreementView.addSubview(agreementCheckBtn) agreementCheckBtn.layoutChain .left(0) .centerY() .width(16) .height(16) agreementView.addSubview(agreementTextView) agreementTextView.layoutChain .leftToRightOfView(agreementCheckBtn, offset: 3) .right(0) .centerY() .bottom(0) let benefitsImgView = UIImageView(image: UIImage(named: "PromotionalActivities/benefits")) benefitsImgView.contentMode = .scaleAspectFill infoView.addSubview(benefitsImgView) benefitsImgView.layoutChain .topToBottomOfView(agreementView, offset: 20) .edgesHorzontal(15) .bottom(15, relation: .greaterThanOrEqual) .heightToWidth(148/375) return view }() lazy var scrollContentView: UIView = { let view = UIView() view.backgroundColor = .clear return view }() lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .plain) tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.estimatedRowHeight = 106 tableView.isScrollEnabled = false tableView.showsVerticalScrollIndicator = false tableView.register(PromotionalNormalCell.self) tableView.register(PromotionalRecommendCell.self) return tableView }() lazy var bottomView: UIView = { let view = UIView() view.backgroundColor = .white view.addSubview(countdownContainer) countdownContainer.layoutChain .top(11) .centerX() view.addSubview(confirmBtn) confirmBtn.layoutChain .edgesHorzontal(30) .bottom(35) .height(50) return view }() lazy var agreementCheckBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Common/checkbox"), for: .normal) btn.setImage(UIImage(named: "Common/checkbox_on"), for: .selected) btn.rx.tap.subscribe(onNext: { [weak btn] in btn?.isSelected.toggle() }).disposed(by: disposeBag) btn.isSelected = true return btn }() lazy var agreementTextView: UITextView = { let tv = UITextView() tv.isEditable = false tv.isScrollEnabled = false tv.backgroundColor = .clear tv.textContainerInset = .zero tv.textContainer.lineFragmentPadding = 0 tv.delegate = self let text = "购买前请先阅读隐私政策和服务条款" let attr = NSMutableAttributedString(string: text, attributes: [ .font: UIFont.systemFont(ofSize: 12), .foregroundColor: UIColor(hexStr: "#999999") ]) let privacyRange = (text as NSString).range(of: "隐私政策") let termsRange = (text as NSString).range(of: "服务条款") let linkColor = UIColor(hexStr: "#16B3FF") attr.addAttributes([.foregroundColor: linkColor, .link: URLManager.shared.privacyPolicyUrl], range: privacyRange) attr.addAttributes([.foregroundColor: linkColor, .link: URLManager.shared.userAgreementUrl], range: termsRange) tv.attributedText = attr tv.linkTextAttributes = [.foregroundColor: linkColor] return tv }() lazy var countdownContainer: UIStackView = { let stack = UIStackView(arrangedSubviews: [countdownPrefixLabel, countdownMinLabel, countdownColonLabel, countdownSecLabel, countdownSuffixLabel]) stack.axis = .horizontal stack.alignment = .center stack.spacing = 0 return stack }() lazy var countdownPrefixLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12) label.textColor = UIColor(hexStr: "#FD501B") label.text = "优惠活动倒计时 " return label }() lazy var countdownMinLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12) label.textColor = .white label.backgroundColor = UIColor(hexStr: "#FD501B") label.layer.cornerRadius = 2 label.clipsToBounds = true label.textAlignment = .center label.text = "15" return label }() lazy var countdownColonLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12) label.textColor = UIColor(hexStr: "#FD501B") label.text = ":" return label }() lazy var countdownSecLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12) label.textColor = .white label.backgroundColor = UIColor(hexStr: "#FD501B") label.layer.cornerRadius = 2 label.clipsToBounds = true label.textAlignment = .center label.text = "00" return label }() lazy var countdownSuffixLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 12) label.textColor = UIColor(hexStr: "#FD501B") label.text = " 限时特惠" return label }() lazy var confirmBtn: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("立即开通", for: .normal) btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) btn.cornerRadius = 25 return btn }() private var countdownRemaining: Int = 15 * 60 private var countdownTimer: Timer? func startCountdown() { countdownRemaining = 15 * 60 updateCountdown() countdownTimer?.invalidate() countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in self?.updateCountdown() } } private func stopCountdown() { countdownTimer?.invalidate() countdownTimer = nil } private func updateCountdown() { if countdownRemaining > 0 { countdownRemaining -= 1 } else { AppRouter.shared.popOrDismiss() return } let min = countdownRemaining / 60 let sec = countdownRemaining % 60 countdownMinLabel.text = String(format: "%02d", min) countdownSecLabel.text = String(format: "%02d", sec) } override func layoutSubviews() { super.layoutSubviews() } override func willMove(toSuperview newSuperview: UIView?) { super.willMove(toSuperview: newSuperview) if newSuperview == nil { stopCountdown() } } override init(frame: CGRect) { super.init(frame: .zero) backgroundColor = .white setupUI() setupRx() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - UIScrollViewDelegate extension PromotionalActivitiesView: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { let maxY = scrollView.contentOffset.y let alpha = maxY / kNaviHeight navBarView.alpha = alpha < 0.0 ? 0.0 : alpha } } // MARK: - UITextViewDelegate extension PromotionalActivitiesView: UITextViewDelegate { func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { AppRouter.push(Route.web, userInfo: ["url": URL.absoluteString]) return false } } // MARK: - PromotionalNormalCell class PromotionalNormalCell: UITableViewCell { func configure(model: VipExpenseModel, isSelected: Bool) { priceLab.text = model.price originPriceLab.text = "原价" + model.origin_price originPriceLab.setupStrikethroughStyle() goodsNameLab.text = model.goods_name tipsLab.text = model.tips2 bgView.image = UIImage(named: isSelected ? "PromotionalActivities/normal_on_bg" : "PromotionalActivities/normal_bg") priceLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor unitLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor } override init(style: CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear setupSubviews() } private func setupSubviews() { contentView.addSubview(bgView) bgView.addSubview(unitLab) bgView.addSubview(priceLab) bgView.addSubview(originPriceLab) bgView.addSubview(goodsNameLab) bgView.addSubview(tipsLab) bgView.layoutChain .top(22) .edgesHorzontal(15) .bottom() priceLab.layoutChain .leftToRightOfView(unitLab, offset: 0) .centerY() unitLab.layoutChain .left(32) .centerY(bgView, offset: 9) originPriceLab.layoutChain .topToBottomOfView(priceLab, offset: -5) .centerX(priceLab) goodsNameLab.layoutChain .leftToRightOfView(priceLab, offset: 27) .bottomToCenterYOfView(bgView) tipsLab.layoutChain .leftToView(goodsNameLab) .topToCenterYOfView(bgView, offset: 3) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } lazy var bgView: UIImageView = { let view = UIImageView() view.backgroundColor = .clear view.contentMode = .scaleAspectFill return view }() lazy var priceLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#FF383C") label.font = .systemFont(ofSize: 40, weight: .bold) return label }() lazy var unitLab: UILabel = { let label = UILabel() label.text = "¥" label.textColor = UIColor(hexStr: "#FF383C") label.font = .systemFont(ofSize: 12, weight: .regular) return label }() lazy var originPriceLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#999999") label.font = .systemFont(ofSize: 10, weight: .regular) return label }() lazy var goodsNameLab: UILabel = { let label = UILabel() label.textColor = ThemeManager.shared.color.titleAuxColor label.font = .systemFont(ofSize: 18, weight: .semibold) return label }() lazy var tipsLab: UILabel = { let label = UILabel() label.textColor = ThemeManager.shared.color.contentColor label.font = .systemFont(ofSize: 14, weight: .regular) return label }() } // MARK: - PromotionalRecommendCell class PromotionalRecommendCell: UITableViewCell { func configure(model: VipExpenseModel, isSelected: Bool) { tipsTitleLab.text = model.tips priceLab.text = model.price originPriceLab.text = "原价" + model.origin_price originPriceLab.setupStrikethroughStyle() goodsNameLab.text = model.goods_name tipsLab.text = model.tips2 bgView.image = UIImage(named: isSelected ? "PromotionalActivities/recommend_on_bg" : "PromotionalActivities/recommend_bg") priceLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor unitLab.textColor = isSelected ? UIColor(hexStr: "#FF383C") : ThemeManager.shared.color.titleAuxColor } override init(style: CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear setupSubviews() } private func setupSubviews() { contentView.addSubview(bgView) bgView.addSubview(unitLab) bgView.addSubview(priceLab) bgView.addSubview(originPriceLab) bgView.addSubview(goodsNameLab) bgView.addSubview(tipsLab) bgView.addSubview(recommendLab) bgView.addSubview(recommendIcon) bgView.addSubview(tipsTitleView) tipsTitleView.addSubview(tipsTitleLab) bgView.layoutChain .top(22) .edgesHorzontal(15) .bottom() tipsTitleView.layoutChain .topToView(bgView, offset: -8) .leftToView(bgView, offset: -6) .width(94) .height(35) tipsTitleLab.layoutChain .centerX() .top(5) priceLab.layoutChain .leftToRightOfView(unitLab, offset: 0) .centerY() unitLab.layoutChain .left(32) .centerY(bgView, offset: 9) originPriceLab.layoutChain .topToBottomOfView(priceLab, offset: -5) .centerX(priceLab) goodsNameLab.layoutChain .leftToRightOfView(priceLab, offset: 27) .bottomToCenterYOfView(bgView, offset: -5) tipsLab.layoutChain .leftToView(goodsNameLab) .topToCenterYOfView(bgView, offset: 8) recommendLab.layoutChain .top(8) .right(24) recommendIcon.layoutChain .rightToLeftOfView(recommendLab, offset: -3) .centerY(recommendLab) .width(13) .height(11) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } lazy var bgView: UIImageView = { let view = UIImageView() view.backgroundColor = .clear view.contentMode = .scaleAspectFill return view }() lazy var priceLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#FF383C") label.font = .systemFont(ofSize: 40, weight: .bold) return label }() lazy var unitLab: UILabel = { let label = UILabel() label.text = "¥" label.textColor = UIColor(hexStr: "#FF383C") label.font = .systemFont(ofSize: 12, weight: .regular) return label }() lazy var originPriceLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#999999") label.font = .systemFont(ofSize: 10, weight: .regular) return label }() lazy var goodsNameLab: UILabel = { let label = UILabel() label.textColor = ThemeManager.shared.color.titleAuxColor label.font = .systemFont(ofSize: 18, weight: .semibold) return label }() lazy var tipsTitleView: UIImageView = { let view = UIImageView() view.backgroundColor = .clear view.image = UIImage(named: "PromotionalActivities/tips_bg") return view }() lazy var tipsTitleLab: UILabel = { let label = UILabel() label.textColor = .white label.font = .systemFont(ofSize: 12, weight: .semibold) return label }() lazy var tipsLab: UILabel = { let label = UILabel() label.textColor = ThemeManager.shared.color.contentColor label.font = .systemFont(ofSize: 14, weight: .regular) return label }() lazy var recommendIcon: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFill view.image = UIImage(named: "PromotionalActivities/recommend_icon") return view }() lazy var recommendLab: UILabel = { let label = UILabel() label.text = "80%的用户选择" label.textColor = .white label.font = .systemFont(ofSize: 12, weight: .medium) return label }() }