// // WalletCells.swift // suixinkan // import SnapKit import UIKit /// 钱包提现记录 Cell。 final class WalletWithdrawRecordCell: UITableViewCell { static let reuseIdentifier = "WalletWithdrawRecordCell" private let cardView = UIView() private let amountLabel = UILabel() private let statusLabel = UILabel() private let timeLabel = UILabel() private let progressView = UIProgressView(progressViewStyle: .bar) private let stepsStack = UIStackView() private let infoLabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUI() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 配置提现记录。 func apply(_ record: WalletWithdrawItem) { amountLabel.text = WalletViewModel.formatAmount(record.amount) statusLabel.text = record.statusLabel.isEmpty ? "--" : record.statusLabel timeLabel.text = record.createdAt.isEmpty ? "--" : record.createdAt let display = WalletWithdrawStatusDisplay(status: record.settlementStatus) let style = statusStyle(for: display.tone) statusLabel.textColor = style.text statusLabel.backgroundColor = style.background progressView.progressTintColor = style.progress let progress = Float(display.progress) / 100 progressView.progress = progress configureSteps(progress: progress, tint: style.progress) progressView.isHidden = !display.showsTimeline stepsStack.isHidden = !display.showsTimeline infoLabel.attributedText = infoText(for: record) infoLabel.isHidden = infoLabel.attributedText?.length == 0 infoLabel.snp.remakeConstraints { make in if record.settlementStatus == 50 { make.top.equalTo(timeLabel.snp.bottom).offset(WalletTokens.compactGap) } else { make.top.equalTo(stepsStack.snp.bottom).offset(10) } make.leading.trailing.equalToSuperview().inset(WalletTokens.cardPadding) make.bottom.equalToSuperview().inset(14) } } private func setupUI() { selectionStyle = .none backgroundColor = .white contentView.backgroundColor = .white cardView.backgroundColor = .white cardView.layer.cornerRadius = WalletTokens.contentRadius cardView.layer.borderWidth = 1 cardView.layer.borderColor = WalletTokens.cardBorder.cgColor cardView.clipsToBounds = true amountLabel.font = WalletTokens.metricBoldFont amountLabel.textColor = WalletTokens.textPrimary statusLabel.font = WalletTokens.captionMediumFont statusLabel.textAlignment = .center statusLabel.layer.cornerRadius = WalletTokens.chipRadius statusLabel.clipsToBounds = true timeLabel.font = WalletTokens.bodyFont timeLabel.textColor = WalletTokens.textMuted progressView.trackTintColor = WalletTokens.progressTrack progressView.layer.cornerRadius = 3 progressView.clipsToBounds = true stepsStack.axis = .horizontal stepsStack.distribution = .equalSpacing stepsStack.alignment = .center infoLabel.font = WalletTokens.bodyFont infoLabel.textColor = WalletTokens.textSecondary infoLabel.numberOfLines = 0 contentView.addSubview(cardView) [amountLabel, statusLabel, timeLabel, progressView, stepsStack, infoLabel].forEach(cardView.addSubview) cardView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 6, left: 16, bottom: 6, right: 16)) } amountLabel.snp.makeConstraints { make in make.top.leading.equalToSuperview().offset(16) make.trailing.lessThanOrEqualTo(statusLabel.snp.leading).offset(-12) } statusLabel.snp.makeConstraints { make in make.centerY.equalTo(amountLabel) make.trailing.equalToSuperview().inset(16) make.height.equalTo(24) make.width.greaterThanOrEqualTo(62) } timeLabel.snp.makeConstraints { make in make.top.equalTo(amountLabel.snp.bottom).offset(6) make.leading.trailing.equalToSuperview().inset(16) } progressView.snp.makeConstraints { make in make.top.equalTo(timeLabel.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(16) make.height.equalTo(6) } stepsStack.snp.makeConstraints { make in make.top.equalTo(progressView.snp.bottom).offset(10) make.leading.trailing.equalToSuperview().inset(16) } infoLabel.snp.makeConstraints { make in make.top.equalTo(stepsStack.snp.bottom).offset(10) make.leading.trailing.equalToSuperview().inset(16) make.bottom.equalToSuperview().inset(14) } } private func configureSteps(progress: Float, tint: UIColor) { stepsStack.arrangedSubviews.forEach { $0.removeFromSuperview() } ["提交申请", "审核中", "打款中", "已完成"].enumerated().forEach { index, title in let label = UILabel() label.text = title label.font = WalletTokens.stepFont let threshold = Float(index) / 3.0 label.textColor = progress >= threshold ? tint : WalletTokens.textDisabled stepsStack.addArrangedSubview(label) } let percent = UILabel() percent.text = "\(Int(progress * 100))%" percent.font = WalletTokens.captionMediumFont percent.textColor = tint stepsStack.addArrangedSubview(percent) } private func statusStyle(for tone: WalletStatusTone) -> (background: UIColor, text: UIColor, progress: UIColor) { switch tone { case .success: (WalletTokens.settlementSuccessBackground, WalletTokens.settlementSuccess, WalletTokens.summaryBackground) case .danger: (WalletTokens.settlementDangerBackground, WalletTokens.progressDanger, WalletTokens.progressDanger) case .warning: (WalletTokens.warningBackground, WalletTokens.warning, WalletTokens.summaryBackground) case .info: (WalletTokens.infoBackground, WalletTokens.summaryBackground, WalletTokens.summaryBackground) } } private func infoText(for record: WalletWithdrawItem) -> NSAttributedString { let result = NSMutableAttributedString() let normal: [NSAttributedString.Key: Any] = [ .font: WalletTokens.bodyFont, .foregroundColor: WalletTokens.textSecondary, ] let success: [NSAttributedString.Key: Any] = [ .font: WalletTokens.bodyFont, .foregroundColor: WalletTokens.settlementSuccess, ] let danger: [NSAttributedString.Key: Any] = [ .font: WalletTokens.bodyFont, .foregroundColor: WalletTokens.progressDanger, ] if (record.settlementStatus == 10 || record.settlementStatus == 40), !record.expectedAt.isEmpty { result.append(NSAttributedString(string: "预计到账时间:\(record.expectedAt)", attributes: normal)) } if (record.settlementStatus == 60 || record.settlementStatus == 30), !record.auditTime.isEmpty { result.append(NSAttributedString(string: "审核时间:\(record.auditTime)", attributes: normal)) } if (record.settlementStatus == 60 || record.settlementStatus == 30), !record.auditRemark.isEmpty { if result.length > 0 { result.append(NSAttributedString(string: "\n")) } result.append(NSAttributedString(string: "备注信息:\(record.auditRemark)", attributes: danger)) } if record.settlementStatus == 50, !record.expectedAt.isEmpty { result.append(NSAttributedString(string: "到账时间:\(record.expectedAt)", attributes: success)) } return result } } /// 钱包收益明细日期分组头 Cell。 final class WalletTransactionHeaderCell: UITableViewCell { static let reuseIdentifier = "WalletTransactionHeaderCell" private let dateLabel = UILabel() private let incomeLabel = UILabel() private let pointsLabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUI() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 配置收益明细分组。 func apply(_ group: EarningDetailGroup) { dateLabel.text = group.date incomeLabel.text = "当日收益:\(WalletViewModel.formatAmount(group.dayAmount))" pointsLabel.text = group.dayPoints > 0 ? "积分+\(group.dayPoints)" : "" } private func setupUI() { selectionStyle = .none backgroundColor = .white dateLabel.font = WalletTokens.bodyFont dateLabel.textColor = WalletTokens.textTertiary incomeLabel.font = WalletTokens.bodyFont incomeLabel.textColor = WalletTokens.textSecondary incomeLabel.textAlignment = .right pointsLabel.font = WalletTokens.bodyFont pointsLabel.textColor = WalletTokens.points pointsLabel.textAlignment = .right contentView.addSubview(dateLabel) contentView.addSubview(incomeLabel) contentView.addSubview(pointsLabel) dateLabel.snp.makeConstraints { make in make.top.bottom.equalToSuperview().inset(8) make.leading.equalToSuperview().offset(16) } pointsLabel.snp.makeConstraints { make in make.centerY.equalTo(dateLabel) make.trailing.equalToSuperview().inset(16) } incomeLabel.snp.makeConstraints { make in make.centerY.equalTo(dateLabel) make.leading.greaterThanOrEqualTo(dateLabel.snp.trailing).offset(8) make.trailing.equalTo(pointsLabel.snp.leading).offset(-12) } } } /// 钱包收益明细记录 Cell。 final class WalletTransactionRecordCell: UITableViewCell { static let reuseIdentifier = "WalletTransactionRecordCell" private let cardView = UIView() private let amountLabel = UILabel() private let pointsLabel = UILabel() private let typeLabel = UILabel() private let orderLabel = UILabel() private let timeLabel = UILabel() private let withdrawLabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUI() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 配置收益明细记录。 func apply(_ item: EarningDetailItem) { amountLabel.text = item.amount.isEmpty ? "" : WalletViewModel.formatAmount(item.amount) pointsLabel.text = item.points > 0 ? "积分+\(item.points)" : "" amountLabel.isHidden = item.amount.isEmpty pointsLabel.isHidden = item.points <= 0 if item.amount.isEmpty, item.points <= 0 { amountLabel.text = "--" amountLabel.isHidden = false } typeLabel.text = item.typeLabel orderLabel.text = item.orderNumberSuffix orderLabel.isHidden = item.orderNumberSuffix.isEmpty timeLabel.text = item.createdAt withdrawLabel.text = item.withdrawLabel withdrawLabel.isHidden = item.withdrawLabel.isEmpty let style = labelStyle(for: item.withdrawLabel) withdrawLabel.textColor = style.text withdrawLabel.backgroundColor = style.background orderLabel.textColor = style.orderText orderLabel.backgroundColor = style.orderBackground } private func setupUI() { selectionStyle = .none backgroundColor = .white cardView.backgroundColor = .white cardView.layer.cornerRadius = WalletTokens.cardRadius cardView.layer.borderWidth = 1 cardView.layer.borderColor = WalletTokens.cardBorder.cgColor cardView.clipsToBounds = true amountLabel.font = WalletTokens.metricMediumFont amountLabel.textColor = WalletTokens.textPrimary pointsLabel.font = WalletTokens.metricMediumFont pointsLabel.textColor = WalletTokens.points pointsLabel.textAlignment = .right typeLabel.font = WalletTokens.bodyFont typeLabel.textColor = WalletTokens.textTertiary orderLabel.font = WalletTokens.captionFont orderLabel.textAlignment = .center orderLabel.layer.cornerRadius = WalletTokens.chipRadius orderLabel.clipsToBounds = true timeLabel.font = WalletTokens.bodyFont timeLabel.textColor = WalletTokens.textSecondary withdrawLabel.font = WalletTokens.captionFont withdrawLabel.textAlignment = .center withdrawLabel.layer.cornerRadius = WalletTokens.chipRadius withdrawLabel.clipsToBounds = true contentView.addSubview(cardView) [amountLabel, pointsLabel, typeLabel, orderLabel, timeLabel, withdrawLabel].forEach(cardView.addSubview) cardView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 6, left: 16, bottom: 6, right: 16)) } amountLabel.snp.makeConstraints { make in make.top.leading.equalToSuperview().offset(16) } pointsLabel.snp.makeConstraints { make in make.centerY.equalTo(amountLabel) make.leading.greaterThanOrEqualTo(amountLabel.snp.trailing).offset(12) make.trailing.equalToSuperview().inset(16) } typeLabel.snp.makeConstraints { make in make.top.equalTo(amountLabel.snp.bottom).offset(10) make.leading.equalToSuperview().offset(16) } orderLabel.snp.makeConstraints { make in make.centerY.equalTo(typeLabel) make.leading.equalTo(typeLabel.snp.trailing).offset(8) make.height.equalTo(20) make.width.greaterThanOrEqualTo(36) } timeLabel.snp.makeConstraints { make in make.top.equalTo(typeLabel.snp.bottom).offset(12) make.leading.equalToSuperview().offset(16) make.bottom.equalToSuperview().inset(16) } withdrawLabel.snp.makeConstraints { make in make.centerY.equalTo(timeLabel) make.trailing.equalToSuperview().inset(16) make.height.equalTo(20) make.width.greaterThanOrEqualTo(48) } } private func labelStyle(for label: String) -> (background: UIColor, text: UIColor, orderBackground: UIColor, orderText: UIColor) { switch label { case "可提现": return (WalletTokens.successBackground, WalletTokens.success, WalletTokens.orderBackground, WalletTokens.primary) default: return (WalletTokens.neutralChipBackground, WalletTokens.textMuted, WalletTokens.neutralChipBackground, WalletTokens.textMuted) } } } /// 钱包列表分页加载 Cell,展示 Android 同款蓝色加载指示器。 final class WalletLoadingCell: UITableViewCell { static let reuseIdentifier = "WalletLoadingCell" private let indicator = UIActivityIndicatorView(style: .medium) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .white indicator.color = WalletTokens.summaryBackground indicator.startAnimating() contentView.addSubview(indicator) indicator.snp.makeConstraints { make in make.center.equalToSuperview() make.top.bottom.equalToSuperview().inset(WalletTokens.cardPadding) } } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }