Files
suixinkan_uikit/suixinkan/UI/Wallet/WalletCells.swift
汉秋 05804ba7d6 完善钱包提现状态展示与举报摄影师列表交互。
对齐 Android 提现进度与时间线映射,优化钱包与积分兑换页刷新逻辑,并补充相关单元测试与真机测试说明。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 16:51:32 +08:00

390 lines
16 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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")
}
}