Files
suixinkan_uikit/suixinkan/UI/Payment/PaymentCollectionDetailsViewController.swift

383 lines
13 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.

//
// PaymentCollectionDetailsViewController.swift
// suixinkan
//
import SnapKit
import UIKit
///
final class PaymentCollectionDetailsViewController: BaseViewController {
private let viewModel = PaymentCollectionDetailsViewModel()
private let paymentAPI = NetworkServices.shared.paymentAPI
private let scrollView = UIScrollView()
private let contentStack = UIStackView()
private let qrCardView = UIView()
private let scenicNameLabel = UILabel()
private let qrImageView = UIImageView()
private let qrPlaceholderView = UIView()
private let qrErrorLabel = UILabel()
private let refreshButton = UIButton(type: .system)
private let setAmountButton = UIButton(type: .system)
private let saveQRButton = UIButton(type: .system)
private let actionRow = UIStackView()
private let infoCardView = UIView()
private let scenicRow = PaymentInfoRowView(title: "景区名称")
private let merchantRow = PaymentInfoRowView(title: "收款方")
private let staffRow = PaymentInfoRowView(title: "员工 ID")
private let recordRow = PaymentNavigationRowView(title: "收款记录")
private let voiceCardView = UIView()
private let voiceTitleLabel = UILabel()
private let voiceSwitch = UISwitch()
private var amountDialog: PaymentSetAmountDialogView?
override func setupNavigationBar() {
title = "收款详情"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
contentStack.axis = .vertical
contentStack.spacing = AppSpacing.md
qrCardView.backgroundColor = .white
qrCardView.layer.cornerRadius = AppRadius.lg
scenicNameLabel.font = .systemFont(ofSize: 16, weight: .medium)
scenicNameLabel.textColor = AppColor.textPrimary
scenicNameLabel.textAlignment = .center
qrImageView.contentMode = .scaleAspectFit
qrImageView.isHidden = true
qrPlaceholderView.backgroundColor = AppColor.inputBackground
qrPlaceholderView.layer.cornerRadius = 24
qrPlaceholderView.isHidden = true
qrErrorLabel.text = "未选景区或网络问题"
qrErrorLabel.font = .systemFont(ofSize: 14)
qrErrorLabel.textColor = AppColor.danger
qrErrorLabel.textAlignment = .center
refreshButton.setTitle("点击刷新", for: .normal)
refreshButton.setTitleColor(AppColor.primary, for: .normal)
refreshButton.titleLabel?.font = .systemFont(ofSize: 14)
actionRow.axis = .horizontal
actionRow.alignment = .center
actionRow.spacing = AppSpacing.xl
configureLinkButton(setAmountButton, title: "设置金额")
configureLinkButton(saveQRButton, title: "保存二维码")
infoCardView.backgroundColor = .white
infoCardView.layer.cornerRadius = AppRadius.lg
recordRow.backgroundColor = .white
recordRow.layer.cornerRadius = AppRadius.lg
voiceCardView.backgroundColor = .white
voiceCardView.layer.cornerRadius = AppRadius.lg
voiceTitleLabel.text = "收款到账语音提醒"
voiceTitleLabel.font = .systemFont(ofSize: 14)
voiceTitleLabel.textColor = UIColor(hex: 0x4B5563)
voiceSwitch.onTintColor = AppColor.primary
view.addSubview(scrollView)
scrollView.addSubview(contentStack)
contentStack.addArrangedSubview(qrCardView)
contentStack.addArrangedSubview(infoCardView)
contentStack.addArrangedSubview(recordRow)
contentStack.addArrangedSubview(voiceCardView)
let qrContentStack = UIStackView(arrangedSubviews: [
scenicNameLabel,
qrImageView,
qrPlaceholderView,
actionRow,
])
qrContentStack.axis = .vertical
qrContentStack.alignment = .center
qrContentStack.spacing = AppSpacing.lg
qrCardView.addSubview(qrContentStack)
qrPlaceholderView.addSubview(qrErrorLabel)
qrPlaceholderView.addSubview(refreshButton)
actionRow.addArrangedSubview(setAmountButton)
actionRow.addArrangedSubview(saveQRButton)
let infoStack = UIStackView(arrangedSubviews: [scenicRow, merchantRow, staffRow])
infoStack.axis = .vertical
infoCardView.addSubview(infoStack)
voiceCardView.addSubview(voiceTitleLabel)
voiceCardView.addSubview(voiceSwitch)
qrContentStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 32, left: 16, bottom: 32, right: 16))
}
scenicNameLabel.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
}
qrImageView.snp.makeConstraints { make in
make.width.height.equalTo(182)
}
qrPlaceholderView.snp.makeConstraints { make in
make.width.height.equalTo(182)
}
qrErrorLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(56)
make.leading.trailing.equalToSuperview().inset(AppSpacing.sm)
}
refreshButton.snp.makeConstraints { make in
make.top.equalTo(qrErrorLabel.snp.bottom).offset(AppSpacing.xs)
make.centerX.equalToSuperview()
}
infoStack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
voiceTitleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalToSuperview()
}
voiceSwitch.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalToSuperview()
}
voiceCardView.snp.makeConstraints { make in
make.height.equalTo(AppSpacing.formRowHeight)
}
}
override func setupConstraints() {
scrollView.snp.makeConstraints { make in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
make.width.equalTo(scrollView.snp.width).offset(-AppSpacing.screenHorizontalInset * 2)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
setAmountButton.addTarget(self, action: #selector(setAmountTapped), for: .touchUpInside)
saveQRButton.addTarget(self, action: #selector(saveQRTapped), for: .touchUpInside)
refreshButton.addTarget(self, action: #selector(refreshTapped), for: .touchUpInside)
recordRow.addTarget(self, action: #selector(recordTapped), for: .touchUpInside)
voiceSwitch.addTarget(self, action: #selector(voiceSwitchChanged), for: .valueChanged)
}
override func viewDidLoad() {
super.viewDidLoad()
Task { await loadData() }
}
private func loadData() async {
showLoading()
await viewModel.loadPayCode(api: paymentAPI)
hideLoading()
}
@MainActor
private func applyViewModel() {
scenicNameLabel.text = viewModel.displayScenicName
scenicRow.setValue(viewModel.displayScenicName)
merchantRow.setValue(viewModel.merchantInfo.company)
staffRow.setValue(viewModel.staffId.isEmpty ? "-" : viewModel.staffId)
voiceSwitch.isOn = viewModel.isVoiceBroadcastOpen
if let image = viewModel.qrImage {
qrImageView.image = image
qrImageView.isHidden = false
qrPlaceholderView.isHidden = true
actionRow.isHidden = false
} else {
qrImageView.isHidden = true
qrPlaceholderView.isHidden = false
actionRow.isHidden = true
}
if viewModel.showAmountDialog {
presentAmountDialogIfNeeded()
} else {
amountDialog?.dismiss()
amountDialog = nil
}
}
private func presentAmountDialogIfNeeded() {
guard amountDialog == nil else {
amountDialog?.apply(amount: viewModel.amount, remark: viewModel.remark)
return
}
let dialog = PaymentSetAmountDialogView()
dialog.onAmountChange = { [weak self] value in
self?.viewModel.updateAmount(value)
}
dialog.onRemarkChange = { [weak self] value in
self?.viewModel.updateRemark(value)
}
dialog.onConfirm = { [weak self] in
self?.viewModel.confirmAmount()
}
dialog.onCancel = { [weak self] in
self?.viewModel.hideAmountDialog()
}
dialog.apply(amount: viewModel.amount, remark: viewModel.remark)
dialog.show(in: view)
amountDialog = dialog
}
private func configureLinkButton(_ button: UIButton, title: String) {
button.setTitle(title, for: .normal)
button.setTitleColor(AppColor.primary, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 14)
}
@objc private func setAmountTapped() {
viewModel.showSetAmountDialog()
}
@objc private func saveQRTapped() {
Task {
await viewModel.saveQRCode()
}
}
@objc private func refreshTapped() {
Task {
showLoading()
await viewModel.refresh(api: paymentAPI)
hideLoading()
}
}
@objc private func recordTapped() {
navigationController?.pushViewController(PaymentCollectionRecordViewController(), animated: true)
}
@objc private func voiceSwitchChanged() {
viewModel.toggleReceiveVoice()
}
}
///
private final class PaymentInfoRowView: UIView {
private let titleLabel = UILabel()
private let valueLabel = UILabel()
private let divider = UIView()
init(title: String) {
super.init(frame: .zero)
titleLabel.text = title
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setValue(_ value: String) {
valueLabel.text = value
}
private func setupUI() {
titleLabel.font = .systemFont(ofSize: 14)
titleLabel.textColor = UIColor(hex: 0x4B5563)
valueLabel.font = .systemFont(ofSize: 14)
valueLabel.textColor = AppColor.textPrimary
valueLabel.textAlignment = .right
valueLabel.numberOfLines = 2
divider.backgroundColor = UIColor(hex: 0xE5E7EB)
addSubview(titleLabel)
addSubview(valueLabel)
addSubview(divider)
snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(AppSpacing.formRowHeight)
}
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalToSuperview()
make.width.equalTo(102)
}
valueLabel.snp.makeConstraints { make in
make.leading.equalTo(titleLabel.snp.trailing).offset(AppSpacing.xs)
make.trailing.equalToSuperview().inset(AppSpacing.md)
make.top.bottom.equalToSuperview().inset(AppSpacing.sm)
}
divider.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(0.5)
}
}
}
///
private final class PaymentNavigationRowView: UIControl {
private let titleLabel = UILabel()
private let chevronView = UIImageView()
init(title: String) {
super.init(frame: .zero)
titleLabel.text = title
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
titleLabel.font = .systemFont(ofSize: 14)
titleLabel.textColor = UIColor(hex: 0x4B5563)
chevronView.image = UIImage(systemName: "chevron.right")
chevronView.tintColor = AppColor.textSecondary
chevronView.contentMode = .scaleAspectFit
addSubview(titleLabel)
addSubview(chevronView)
snp.makeConstraints { make in
make.height.equalTo(AppSpacing.formRowHeight)
}
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalToSuperview()
}
chevronView.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalToSuperview()
make.width.height.equalTo(16)
}
}
}