171 lines
7.1 KiB
Swift
171 lines
7.1 KiB
Swift
import SnapKit
|
||
import UIKit
|
||
|
||
/// 收款页中的线下收款入口、今日汇总和逾期提醒区域。
|
||
@MainActor
|
||
final class OfflineCollectionHomeView: UIView {
|
||
var onRegister: (() -> Void)?
|
||
var onOpenToday: (() -> Void)?
|
||
var onOpenOverdue: (() -> Void)?
|
||
var onRetry: (() -> Void)?
|
||
|
||
private let stack = UIStackView()
|
||
private let statusButton = UIButton(type: .system)
|
||
private let overdueCard = OfflineCollectionHomeCard()
|
||
private let registerCard = OfflineCollectionHomeCard()
|
||
private let todayCard = OfflineCollectionHomeCard()
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
stack.axis = .vertical
|
||
stack.spacing = AppSpacing.md
|
||
let title = UILabel()
|
||
title.text = "线下收款"
|
||
title.font = .systemFont(ofSize: 16, weight: .semibold)
|
||
title.textColor = AppColor.textPrimary
|
||
stack.addArrangedSubview(title)
|
||
stack.addArrangedSubview(statusButton)
|
||
stack.addArrangedSubview(overdueCard)
|
||
stack.addArrangedSubview(registerCard)
|
||
stack.addArrangedSubview(todayCard)
|
||
addSubview(stack)
|
||
stack.snp.makeConstraints { make in make.edges.equalToSuperview() }
|
||
title.snp.makeConstraints { make in make.height.equalTo(24) }
|
||
|
||
statusButton.configuration = .plain()
|
||
statusButton.configuration?.baseForegroundColor = AppColor.primary
|
||
statusButton.addTarget(self, action: #selector(retryTapped), for: .touchUpInside)
|
||
statusButton.isHidden = true
|
||
|
||
registerCard.apply(
|
||
title: "线下收款登记",
|
||
detail: "线下收款后,请及时登记并在当日完成补缴",
|
||
action: "去登记",
|
||
image: "plus.circle.fill",
|
||
tint: AppColor.primary,
|
||
background: .white
|
||
)
|
||
registerCard.addTarget(self, action: #selector(registerTapped), for: .touchUpInside)
|
||
todayCard.addTarget(self, action: #selector(todayTapped), for: .touchUpInside)
|
||
overdueCard.addTarget(self, action: #selector(overdueTapped), for: .touchUpInside)
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||
|
||
/// 根据真实统计接口状态刷新区域。
|
||
func apply(
|
||
today: OfflineDailySummary,
|
||
overdueDayCount: Int,
|
||
overdueRecordCount: Int,
|
||
overdueAmountFen: Int,
|
||
errorMessage: String?
|
||
) {
|
||
if let errorMessage {
|
||
statusButton.configuration?.title = "\(errorMessage) 点击重试"
|
||
statusButton.configuration?.showsActivityIndicator = false
|
||
statusButton.isUserInteractionEnabled = true
|
||
statusButton.isHidden = false
|
||
statusButton.accessibilityTraits.insert(.button)
|
||
} else {
|
||
statusButton.isHidden = true
|
||
}
|
||
|
||
let hasOverdue = overdueDayCount > 0
|
||
overdueCard.isHidden = !hasOverdue
|
||
if hasOverdue {
|
||
overdueCard.apply(
|
||
title: "存在逾期未补缴",
|
||
detail: "\(overdueDayCount) 个营业日,共 \(overdueRecordCount) 笔,待补缴 \(OfflineCollectionMoney.display(overdueAmountFen))",
|
||
action: "立即处理",
|
||
image: "exclamationmark.circle.fill",
|
||
tint: AppColor.danger,
|
||
background: UIColor(hex: 0xFFF1F0)
|
||
)
|
||
}
|
||
|
||
let settled = today.totalCount > 0 && today.pendingCount == 0
|
||
todayCard.apply(
|
||
title: "今日待补缴 \(OfflineCollectionMoney.display(today.pendingAmountFen))",
|
||
detail: "\(today.pendingCount) 笔 · 今日已登记 \(OfflineCollectionMoney.display(today.totalAmountFen))\(settled ? " · 已结清" : "")",
|
||
action: today.pendingCount > 0 ? "去补缴" : "查看明细",
|
||
image: "calendar",
|
||
tint: today.pendingCount > 0 ? UIColor(hex: 0xD97706) : AppColor.primary,
|
||
background: .white
|
||
)
|
||
}
|
||
|
||
@objc private func retryTapped() { onRetry?() }
|
||
@objc private func registerTapped() { onRegister?() }
|
||
@objc private func todayTapped() { onOpenToday?() }
|
||
@objc private func overdueTapped() { onOpenOverdue?() }
|
||
}
|
||
|
||
/// 线下收款首页区域的统一可点击卡片。
|
||
private final class OfflineCollectionHomeCard: UIControl {
|
||
private let iconView = UIImageView()
|
||
private let titleLabel = UILabel()
|
||
private let detailLabel = UILabel()
|
||
private let actionLabel = UILabel()
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
layer.cornerRadius = AppRadius.lg
|
||
clipsToBounds = true
|
||
isAccessibilityElement = true
|
||
iconView.contentMode = .scaleAspectFit
|
||
titleLabel.font = .systemFont(ofSize: 15, weight: .semibold)
|
||
titleLabel.textColor = AppColor.textPrimary
|
||
detailLabel.font = .systemFont(ofSize: 13)
|
||
detailLabel.textColor = AppColor.textSecondary
|
||
detailLabel.numberOfLines = 0
|
||
actionLabel.font = .systemFont(ofSize: 13, weight: .semibold)
|
||
actionLabel.textColor = AppColor.primary
|
||
actionLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||
let textStack = UIStackView(arrangedSubviews: [titleLabel, detailLabel])
|
||
textStack.axis = .vertical
|
||
textStack.spacing = 5
|
||
[iconView, titleLabel, detailLabel, actionLabel, textStack].forEach {
|
||
$0.isUserInteractionEnabled = false
|
||
}
|
||
addSubview(iconView)
|
||
addSubview(textStack)
|
||
addSubview(actionLabel)
|
||
iconView.snp.makeConstraints { make in make.leading.equalToSuperview().inset(AppSpacing.md); make.centerY.equalToSuperview(); make.width.height.equalTo(30) }
|
||
textStack.snp.makeConstraints { make in make.leading.equalTo(iconView.snp.trailing).offset(AppSpacing.sm); make.top.bottom.equalToSuperview().inset(AppSpacing.md); make.trailing.lessThanOrEqualTo(actionLabel.snp.leading).offset(-AppSpacing.sm) }
|
||
actionLabel.snp.makeConstraints { make in make.trailing.equalToSuperview().inset(AppSpacing.md); make.centerY.equalToSuperview() }
|
||
snp.makeConstraints { make in make.height.greaterThanOrEqualTo(86) }
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||
|
||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||
guard isEnabled,
|
||
isUserInteractionEnabled,
|
||
!isHidden,
|
||
alpha > 0.01,
|
||
self.point(inside: point, with: event) else { return nil }
|
||
return self
|
||
}
|
||
|
||
override var isHighlighted: Bool {
|
||
didSet {
|
||
UIView.animate(withDuration: 0.12) {
|
||
self.alpha = self.isHighlighted ? 0.78 : 1
|
||
}
|
||
}
|
||
}
|
||
|
||
func apply(title: String, detail: String, action: String, image: String, tint: UIColor, background: UIColor) {
|
||
titleLabel.text = title
|
||
detailLabel.text = detail
|
||
actionLabel.text = "\(action) ›"
|
||
iconView.image = UIImage(systemName: image)
|
||
iconView.tintColor = tint
|
||
backgroundColor = background
|
||
accessibilityLabel = "\(title),\(detail),\(action)"
|
||
accessibilityTraits = .button
|
||
}
|
||
}
|