VIP购买,各种历史记录页面完善

This commit is contained in:
zeng 2025-05-06 17:10:49 +08:00
parent 65a0753c20
commit e4e92ee9bf
25 changed files with 430 additions and 27 deletions

View File

@ -0,0 +1,24 @@
//
// Date+SPAdd.swift
// MoviaBox
//
// Created by on 2025/5/6.
//
import UIKit
extension Date {
/// yyyy-MM-dd HH-mm-ss
func format(dateFormat: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = dateFormat
return formatter.string(from: self)
}
func dateDifference(date: Date) -> Int {
let dateComponents = Calendar.current.dateComponents([.day], from: self, to: date)
return dateComponents.day ?? 0
}
}

View File

@ -8,6 +8,10 @@
import UIKit
class SPWalletAPI: NSObject {
enum BuyType: String {
case coins = "coins"
case vip = "vip"
}
///
static func requestPayTemplate(completer: ((_ model: SPPayTemplateModel?) -> Void)?) {
@ -30,9 +34,14 @@ class SPWalletAPI: NSObject {
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPIAPOrderModel>) in
if let message = response.data?.message, message.count > 0 {
SPToast.show(text: message)
completer?(nil)
} else {
completer?(response.data)
}
}
}
///
static func requestVerifyOrder(orderCode: String, payId: String, productId: String, purchaseToken: String, completer: ((_ model: SPIAPVerifyModel?) -> Void)?) {
@ -50,4 +59,46 @@ class SPWalletAPI: NSObject {
}
}
///
static func reuqestSendCoinRecord(page: Int, completer: ((_ listModel: SPListModel<SPRewardCoinsRecordModel>?) -> Void)?) {
var param = SPNetworkParameters(path: "/sendCoinList")
param.parameters = [
"page_size" : 20,
"current_page" : page
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPRewardCoinsRecordModel>>) in
completer?(response.data)
}
}
///
static func requestRechargeRecord(buyType: BuyType, page: Int, completer: ((_ listModel: SPListModel<SPRechargeRecordModel>?) -> Void)?) {
var param = SPNetworkParameters(path: "/getCustomerOrder")
param.method = .get
param.parameters = [
"page_size" : 20,
"current_page" : page,
"buy_type" : buyType.rawValue
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPRechargeRecordModel>>) in
completer?(response.data)
}
}
///
static func requestBuyRecords(page: Int, completer: ((_ listModel: SPListModel<SPBuyRecordsModel>?) -> Void)?) {
var param = SPNetworkParameters(path: "/getCustomerBuyRecords")
param.method = .get
param.parameters = [
"page_size" : 20,
"current_page" : page,
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPBuyRecordsModel>>) in
completer?(response.data)
}
}
}

View File

@ -57,6 +57,8 @@ class SPMineViewController: SPViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.headerView.userInfo = SPLoginManager.manager.userInfo
if isHaveEntered {
requestData()
}

View File

@ -28,6 +28,7 @@ class SPMineMemberView: UIView {
var userInfo: SPUserInfo? {
didSet {
if userInfo?.is_vip == true {
yesView.userInfo = userInfo
yesView.isHidden = false
noView.isHidden = true
bgImageView.image = UIImage(named: "member_bg_image_02")

View File

@ -9,6 +9,17 @@ import UIKit
class SPMineMemberYesView: UIView {
var userInfo: SPUserInfo? {
didSet {
let date = Date(timeIntervalSince1970: userInfo?.vip_end_time ?? 0)
#if DEBUG
expirationTimeLabel.text = String(format: "VlP expiration time : %@".localized, date.format(dateFormat: "yyyy-MM-dd HH:mm:ss"))
#else
expirationTimeLabel.text = String(format: "VlP expiration time : %@".localized, date.format(dateFormat: "yyyy-MM-dd"))
#endif
}
}
//MARK: UI
private lazy var iconImageView: UIImageView = {
@ -59,8 +70,6 @@ class SPMineMemberYesView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
// titleLabelGradientLayer.frame = titleLabel.bounds
}
}

View File

@ -107,6 +107,9 @@ class SPPlayerControlView: UIView {
return button
}()
///
// private lazy var lock
deinit {
viewModel?.removeObserver(self, forKeyPath: "isPlaying")
NotificationCenter.default.removeObserver(self)

View File

@ -9,12 +9,23 @@ import UIKit
class SPCoinOrderRecordViewController: SPViewController {
private var dataArr: [SPRechargeRecordModel] = []
private var page: Int = 1
//MARK: UI
private lazy var tableView: SPTableView = {
let tableView = SPTableView(frame: .zero, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 70
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
tableView.sp_addRefreshHeader { [weak self] in
self?.handleHeaderRefresh(nil)
}
tableView.sp_addRefreshBackFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil)
}
SPCoinOrderRecordCell.registerCell(tableView: tableView)
return tableView
}()
@ -24,10 +35,21 @@ class SPCoinOrderRecordViewController: SPViewController {
super.viewDidLoad()
setBackgroundView(isShowGradient: false, bgImage: nil, backgroundColor: .clear)
requestDataArr(page: 1, completer: nil)
_setupUI()
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: 1) { [weak self] in
self?.tableView.sp_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: self.page + 1) { [weak self] in
self?.tableView.sp_endFooterRefreshing()
}
}
}
@ -48,11 +70,33 @@ extension SPCoinOrderRecordViewController: UITableViewDelegate, UITableViewDataS
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = SPCoinOrderRecordCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
cell.model = self.dataArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
return self.dataArr.count
}
}
extension SPCoinOrderRecordViewController {
private func requestDataArr(page: Int, completer: (() -> Void)?) {
SPWalletAPI.requestRechargeRecord(buyType: .coins, page: page) { [weak self] listModel in
guard let self = self else { return }
if let list = listModel?.list {
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.page = page
self.tableView.reloadData()
}
completer?()
}
}
}

View File

@ -10,6 +10,9 @@ import UIKit
///
class SPConsumptionRecordsViewController: SPViewController {
private var dataArr: [SPBuyRecordsModel] = []
private var page: Int = 1
//MARK: UI
private lazy var tableView: SPTableView = {
let tableView = SPTableView(frame: .zero, style: .plain)
@ -17,6 +20,13 @@ class SPConsumptionRecordsViewController: SPViewController {
tableView.dataSource = self
tableView.rowHeight = 72
tableView.separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32)
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
tableView.sp_addRefreshHeader { [weak self] in
self?.handleHeaderRefresh(nil)
}
tableView.sp_addRefreshBackFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil)
}
SPConsumptionRecordsCell.registerCell(tableView: tableView)
return tableView
}()
@ -27,6 +37,8 @@ class SPConsumptionRecordsViewController: SPViewController {
self.edgesForExtendedLayout = .top
_setupUI()
requestDataArr(page: 1, completer: nil)
}
override func viewWillAppear(_ animated: Bool) {
@ -35,6 +47,18 @@ class SPConsumptionRecordsViewController: SPViewController {
setNavigationNormalStyle(backgroundColor: .clear, isTranslucent: true)
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: 1) { [weak self] in
self?.tableView.sp_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: self.page + 1) { [weak self] in
self?.tableView.sp_endFooterRefreshing()
}
}
}
extension SPConsumptionRecordsViewController {
@ -52,10 +76,32 @@ extension SPConsumptionRecordsViewController {
extension SPConsumptionRecordsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = SPConsumptionRecordsCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
cell.model = self.dataArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
return self.dataArr.count
}
}
extension SPConsumptionRecordsViewController {
private func requestDataArr(page: Int, completer: (() -> Void)?) {
SPWalletAPI.requestBuyRecords(page: page) { [weak self] listModel in
guard let self = self else { return }
if let list = listModel?.list {
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.page = page
self.tableView.reloadData()
}
completer?()
}
}
}

View File

@ -10,12 +10,22 @@ import UIKit
//
class SPRewardCoinsViewController: SPViewController {
private var dataArr: [SPRewardCoinsRecordModel] = []
private var page: Int = 1
//MARK: UI
private lazy var tableView: SPTableView = {
let tableView = SPTableView(frame: .zero, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 96
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
tableView.sp_addRefreshHeader { [weak self] in
self?.handleHeaderRefresh(nil)
}
tableView.sp_addRefreshBackFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil)
}
SPRewardCoinsCell.registerCell(tableView: tableView)
return tableView
}()
@ -26,6 +36,8 @@ class SPRewardCoinsViewController: SPViewController {
self.edgesForExtendedLayout = .top
_setupUI()
requestDataArr(page: 1, completer: nil)
}
override func viewWillAppear(_ animated: Bool) {
@ -35,6 +47,17 @@ class SPRewardCoinsViewController: SPViewController {
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: 1) { [weak self] in
self?.tableView.sp_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: self.page + 1) { [weak self] in
self?.tableView.sp_endFooterRefreshing()
}
}
}
@ -55,10 +78,32 @@ extension SPRewardCoinsViewController {
extension SPRewardCoinsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = SPRewardCoinsCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
cell.model = self.dataArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
return self.dataArr.count
}
}
extension SPRewardCoinsViewController {
private func requestDataArr(page: Int, completer: (() -> Void)?) {
SPWalletAPI.reuqestSendCoinRecord(page: page) { [weak self] listModel in
guard let self = self else { return }
if let list = listModel?.list {
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.page = page
self.tableView.reloadData()
}
completer?()
}
}
}

View File

@ -24,11 +24,18 @@ class SPStoreViewController: SPViewController {
private lazy var rechargeView: SPCoinRechargeView = {
let view = SPCoinRechargeView()
view.userInfo = SPLoginManager.manager.userInfo
view.rechargeFinishHandle = { [weak self] in
self?.buyFinish()
}
return view
}()
private lazy var memberView: SPMemberRechargeView = {
let view = SPMemberRechargeView()
view.buyFinishHandle = { [weak self] in
self?.buyFinish()
}
return view
}()
@ -80,6 +87,12 @@ extension SPStoreViewController {
}
@objc private func buyFinish() {
SPLoginManager.manager.updateUserInfo { [weak self] in
self?.rechargeView.userInfo = SPLoginManager.manager.userInfo
}
}
}
extension SPStoreViewController {

View File

@ -9,12 +9,22 @@ import UIKit
class SPVIPOrderRecordViewController: SPViewController {
private var dataArr: [SPRechargeRecordModel] = []
private var page: Int = 1
//MARK: UI
private lazy var tableView: SPTableView = {
let tableView = SPTableView(frame: .zero, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 74
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
tableView.sp_addRefreshHeader { [weak self] in
self?.handleHeaderRefresh(nil)
}
tableView.sp_addRefreshBackFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil)
}
SPVIPOrderRecordCell.registerCell(tableView: tableView)
return tableView
}()
@ -24,10 +34,22 @@ class SPVIPOrderRecordViewController: SPViewController {
super.viewDidLoad()
setBackgroundView(isShowGradient: false, bgImage: nil, backgroundColor: .clear)
requestDataArr(page: 1, completer: nil)
_setupUI()
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: 1) { [weak self] in
self?.tableView.sp_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
self.requestDataArr(page: self.page + 1) { [weak self] in
self?.tableView.sp_endFooterRefreshing()
}
}
}
@ -47,11 +69,33 @@ extension SPVIPOrderRecordViewController: UITableViewDelegate, UITableViewDataSo
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = SPVIPOrderRecordCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
cell.model = self.dataArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
return self.dataArr.count
}
}
extension SPVIPOrderRecordViewController {
private func requestDataArr(page: Int, completer: (() -> Void)?) {
SPWalletAPI.requestRechargeRecord(buyType: .vip, page: page) { [weak self] listModel in
guard let self = self else { return }
if let list = listModel?.list {
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.page = page
self.tableView.reloadData()
}
completer?()
}
}
}

View File

@ -0,0 +1,15 @@
//
// SPBuyRecordsModel.swift
// MoviaBox
//
// Created by on 2025/5/6.
//
import UIKit
import SmartCodable
class SPBuyRecordsModel: SPModel, SmartCodable {
}

View File

@ -0,0 +1,16 @@
//
// SPRechargeRecordModel.swift
// MoviaBox
//
// Created by on 2025/5/6.
//
import UIKit
import SmartCodable
class SPRechargeRecordModel: SPModel, SmartCodable {
var type: String?
var value: String?
var created_at: String?
}

View File

@ -0,0 +1,19 @@
//
// SPRewardCoinsRecordModel.swift
// MoviaBox
//
// Created by on 2025/5/6.
//
import UIKit
import SmartCodable
class SPRewardCoinsRecordModel: SPModel, SmartCodable {
var id: String?
var created_at: String?
var type: String?
var left_coins: String?
var expired_time: TimeInterval?
var coins: Int?
var diff_datetime: String?
}

View File

@ -9,6 +9,14 @@ import UIKit
class SPCoinOrderRecordCell: SPTableViewCell {
var model: SPRechargeRecordModel? {
didSet {
titleLabel.text = "Recharge Coins".localized
timeLabel.text = model?.created_at
coinLabel.text = "+\(model?.value ?? "0")"
}
}
//MARK: UI
private lazy var titleLabel: UILabel = {
let label = UILabel()
@ -38,9 +46,7 @@ class SPCoinOrderRecordCell: SPTableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
titleLabel.text = "Recharge Coins"
timeLabel.text = "2024-6-10 23:41:18"
coinLabel.text = "+20000"
_setupUI()
}

View File

@ -24,6 +24,12 @@ class SPCoinRechargeView: UIView {
}
}
var userInfo: SPUserInfo? {
didSet {
coinLabel.text = "\(userInfo?.coin_left_total ?? 0)"
}
}
//MARK: UI
private lazy var coinNameLabel: UILabel = {
let label = UILabel()
@ -67,7 +73,7 @@ class SPCoinRechargeView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
coinLabel.text = "\(SPLoginManager.manager.userInfo?.coin_left_total ?? 0)"
_setupUI()
}
@ -131,10 +137,6 @@ extension SPCoinRechargeView: UICollectionViewDelegate, UICollectionViewDataSour
guard let model = self.dataArr?[indexPath.row] else { return }
SPIAPManager.manager.startRecharge(model: model) { [weak self] finish in
SPLoginManager.manager.updateUserInfo {
self?.coinLabel.text = "\(SPLoginManager.manager.userInfo?.coin_left_total ?? 0)"
}
if finish {
self?.rechargeFinishHandle?()
}

View File

@ -9,6 +9,12 @@ import UIKit
class SPConsumptionRecordsCell: SPTableViewCell {
var model: SPBuyRecordsModel? {
didSet {
}
}
//MARK: UI
private lazy var titleLabel: UILabel = {
let label = UILabel()

View File

@ -16,6 +16,9 @@ class SPMemberRechargeView: UIView {
return CGSize(width: kSPScreenWidth, height: height)
}
///
var buyFinishHandle: (() -> Void)?
var dataArr: [SPPayTemplateItem]? {
didSet {
self.invalidateIntrinsicContentSize()
@ -96,6 +99,13 @@ extension SPMemberRechargeView: UICollectionViewDelegate, UICollectionViewDataSo
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let model = dataArr?[indexPath.row] else { return }
SPIAPManager.manager.startRecharge(model: model) { [weak self] finish in
if finish {
self?.buyFinishHandle?()
}
}
}
}

View File

@ -9,6 +9,42 @@ import UIKit
class SPRewardCoinsCell: SPTableViewCell {
var model: SPRewardCoinsRecordModel? {
didSet {
timeLabel.text = model?.created_at
coinLabel.text = "+\(model?.coins ?? 0)"
remainingLabel.text = model?.left_coins
nameLabel.text = "Check in".localized
let expireDate = Date(timeIntervalSince1970: model?.expired_time ?? 0)
let nowDate = Date()
let days = nowDate.dateDifference(date: expireDate)
if days > 0 {
expireLabel.text = String(format: "Expires in %@ days".localized, "\(days)")
expireLabel.textColor = .colorFF3232()
expireIconImageView.isHidden = false
expireLabel.snp.remakeConstraints { make in
make.centerY.equalTo(expireIconImageView)
make.left.equalTo(expireIconImageView.snp.right).offset(4)
}
} else {
expireLabel.text = "Expired".localized
expireLabel.textColor = .colorFFFFFF()
expireIconImageView.isHidden = true
expireLabel.snp.makeConstraints { make in
make.centerY.equalTo(expireIconImageView)
make.left.equalTo(timeLabel)
}
}
}
}
//MARK: UI
private lazy var timeLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 14)
@ -51,11 +87,7 @@ class SPRewardCoinsCell: SPTableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
timeLabel.text = "2024-6-10 23:41:18"
nameLabel.text = "Check in"
expireLabel.text = "Expires in 30 days"
coinLabel.text = "+30"
remainingLabel.text = "Remaining30"
_setupUI()
}

View File

@ -9,6 +9,14 @@ import UIKit
class SPVIPOrderRecordCell: SPTableViewCell {
var model: SPRechargeRecordModel? {
didSet {
titleLabel.text = "Purchase VIP".localized
timeLabel.text = model?.created_at
dayLabel.text = model?.value
}
}
//MARK: UI
private lazy var titleLabel: UILabel = {
let label = UILabel()
@ -35,9 +43,7 @@ class SPVIPOrderRecordCell: SPTableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
titleLabel.text = "Purchase VIP"
timeLabel.text = "2024-6-10 23:41:18"
dayLabel.text = "+30 days"
_setupUI()
}

View File

@ -66,7 +66,7 @@ extension SPIAPManager: JXIAPManagerDelegate {
SPWalletAPI.requestVerifyOrder(orderCode: orderCode, payId: payId, productId: productId, purchaseToken: receipt) { model in
SPHUD.dismiss()
if let model = model {
if model?.status == "success" {
self.orderCode = nil
self.payId = nil

View File

@ -14,4 +14,7 @@ class SPIAPOrderModel: SPModel, SmartCodable {
var order_code: String?
var money: String?
var is_backhaul: String?
var code: Int?
var message: String?
}

View File

@ -18,7 +18,7 @@ class SPUserInfo: SPModel, SmartCodable, NSSecureCoding {
var is_tourist: Bool?
var family_name: String?
var giving_name: String?
var vip_end_time: String?
var vip_end_time: TimeInterval?
var third_access_id: String?
var is_vip: Bool?
var coin_left_total: Int?

View File

@ -79,6 +79,12 @@
"Signout" = "Signout";
"Confirm logout?" = "Confirm logout?";
"Invalid in-app purchase" = "Invalid in-app purchase";
"VlP expiration time : %@" = "VlP expiration time : %@";
"Expires in %@ days" = "Expires in %@ days";
"Expired" = "Expired";
"Check in" = "Check in";
"Recharge Coins" = "Recharge Coins";
"Purchase VIP" = "Purchase VIP";
"kLoginAgreementText" = "By continuing, you agree to the User Agreement and Privacy Policy";