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 } }