feat: 完成9.7线下收款登记与日清

This commit is contained in:
2026-08-26 09:53:48 +08:00
parent dca4bd5a20
commit 825a0448cc
24 changed files with 2663 additions and 6 deletions
@@ -12,6 +12,11 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
private let viewModel = PaymentCollectionDetailsViewModel()
private let paymentAPI = NetworkServices.shared.paymentAPI
private let offlineCollectionAPI = NetworkServices.shared.offlineCollectionAPI
private lazy var offlineCollectionViewModel = OfflineCollectionHomeViewModel(
context: .current(),
api: offlineCollectionAPI
)
private let scrollView = UIScrollView()
private let contentContainerView = UIView()
@@ -48,10 +53,12 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
private let voiceCardView = UIView()
private let voiceTitleLabel = UILabel()
private let voiceSwitch = UISwitch()
private let offlineCollectionView = OfflineCollectionHomeView()
private var amountDialog: PaymentSetAmountDialogView?
private var appliedBrandConfig: PayPageConfig?
private var appliedBrandingRefreshVersion = -1
private var isShowingOfflineCollectionLoading = false
private var previousStandardAppearance: UINavigationBarAppearance?
private var previousScrollEdgeAppearance: UINavigationBarAppearance?
@@ -101,8 +108,8 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
let usesBranding = viewModel.usesNalatiBranding
view.backgroundColor = usesBranding ? .clear : AppColor.pageBackground
scrollView.showsVerticalScrollIndicator = !usesBranding
scrollView.isScrollEnabled = !usesBranding
scrollView.alwaysBounceVertical = false
scrollView.isScrollEnabled = true
scrollView.alwaysBounceVertical = true
contentStack.axis = .vertical
contentStack.spacing = usesBranding ? brandSectionSpacing : AppSpacing.md
@@ -201,6 +208,7 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
contentStack.addArrangedSubview(recordRow)
contentStack.addArrangedSubview(voiceCardView)
}
contentStack.addArrangedSubview(offlineCollectionView)
let qrDisplayView = usesBranding ? qrContainerView : qrImageView
let qrContentStack = UIStackView(arrangedSubviews: [
@@ -330,15 +338,14 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
make.edges.equalToSuperview()
make.width.equalTo(scrollView.snp.width)
if viewModel.usesNalatiBranding {
make.height.equalTo(scrollView.snp.height)
make.height.greaterThanOrEqualTo(scrollView.snp.height)
}
}
contentStack.snp.makeConstraints { make in
make.width.equalTo(scrollView.snp.width).offset(-AppSpacing.screenHorizontalInset * 2)
if viewModel.usesNalatiBranding {
make.centerX.centerY.equalToSuperview()
make.top.greaterThanOrEqualToSuperview()
make.bottom.lessThanOrEqualToSuperview()
make.centerX.equalToSuperview()
make.top.bottom.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
} else {
make.edges.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
}
@@ -358,6 +365,22 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
refreshButton.addTarget(self, action: #selector(refreshTapped), for: .touchUpInside)
recordRow.addTarget(self, action: #selector(recordTapped), for: .touchUpInside)
voiceSwitch.addTarget(self, action: #selector(voiceSwitchChanged), for: .valueChanged)
offlineCollectionViewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyOfflineCollection() }
}
offlineCollectionView.onRetry = { [weak self] in
Task { await self?.offlineCollectionViewModel.load() }
}
offlineCollectionView.onRegister = { [weak self] in self?.openOfflineRegistration() }
offlineCollectionView.onOpenToday = { [weak self] in
guard let self, let date = self.offlineCollectionViewModel.todayBusinessDate else { return }
self.openOfflineDaily(date: date)
}
offlineCollectionView.onOpenOverdue = { [weak self] in
guard let self, let date = self.offlineCollectionViewModel.earliestOverdueBusinessDate else { return }
self.openOfflineDaily(date: date)
}
}
override func viewDidLoad() {
@@ -369,10 +392,13 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
applyBrandNavigationAppearanceIfNeeded()
applyOfflineCollection()
Task { await offlineCollectionViewModel.load() }
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
setOfflineCollectionLoadingVisible(false)
restoreNavigationAppearanceIfNeeded()
}
@@ -432,6 +458,26 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
amountDialog?.dismiss()
amountDialog = nil
}
applyOfflineCollection()
}
@MainActor
private func applyOfflineCollection() {
setOfflineCollectionLoadingVisible(offlineCollectionViewModel.isLoading)
offlineCollectionView.apply(
today: offlineCollectionViewModel.todaySummary,
overdueDayCount: offlineCollectionViewModel.overdueDates.count,
overdueRecordCount: offlineCollectionViewModel.overdueRecordCount,
overdueAmountFen: offlineCollectionViewModel.overdueAmountFen,
errorMessage: offlineCollectionViewModel.errorMessage
)
}
@MainActor
private func setOfflineCollectionLoadingVisible(_ visible: Bool) {
guard visible != isShowingOfflineCollectionLoading else { return }
isShowingOfflineCollectionLoading = visible
visible ? showLoading() : hideLoading()
}
private func setActionRowAvailable(_ isAvailable: Bool) {
@@ -668,6 +714,23 @@ final class PaymentCollectionDetailsViewController: BaseViewController {
@objc private func voiceSwitchChanged() {
viewModel.toggleReceiveVoice()
}
private func openOfflineRegistration() {
let controller = OfflineCollectionRegistrationViewController(
context: offlineCollectionViewModel.context,
api: offlineCollectionAPI
)
navigationController?.pushViewController(controller, animated: true)
}
private func openOfflineDaily(date: String) {
let controller = OfflineCollectionDailyViewController(
businessDate: date,
context: offlineCollectionViewModel.context,
api: offlineCollectionAPI
)
navigationController?.pushViewController(controller, animated: true)
}
}
/// 收款详情信息行。