Add instant payment collection flow aligned with Android.

Implement收款详情与收款记录 pages with pay-code API, QR generation, amount setting, and record grouping so merchants can collect payments from the home quick action.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 09:29:03 +08:00
parent 731de7d7f0
commit d8329d19fd
17 changed files with 1548 additions and 1 deletions

View File

@ -0,0 +1,205 @@
//
// PaymentSetAmountDialogView.swift
// suixinkan
//
import SnapKit
import UIKit
/// Android `SetAmountDialog`
final class PaymentSetAmountDialogView: UIView {
var onConfirm: (() -> Void)?
var onCancel: (() -> Void)?
var onAmountChange: ((String) -> Void)?
var onRemarkChange: ((String) -> Void)?
private let dimView = UIView()
private let cardView = UIView()
private let titleLabel = UILabel()
private let amountContainer = UIView()
private let currencyLabel = UILabel()
private let amountField = UITextField()
private let remarkTitleLabel = UILabel()
private let remarkField = UITextView()
private let remarkCountLabel = UILabel()
private let buttonStack = UIStackView()
private let cancelButton = UIButton(type: .system)
private let confirmButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(amount: String, remark: String) {
if amountField.text != amount {
amountField.text = amount
}
if remarkField.text != remark {
remarkField.text = remark
}
remarkCountLabel.text = "\(remark.count)/200"
}
func show(in parent: UIView) {
frame = parent.bounds
autoresizingMask = [.flexibleWidth, .flexibleHeight]
alpha = 0
parent.addSubview(self)
UIView.animate(withDuration: 0.2) {
self.alpha = 1
}
}
func dismiss() {
endEditing(true)
UIView.animate(withDuration: 0.2, animations: {
self.alpha = 0
}, completion: { _ in
self.removeFromSuperview()
})
}
private func setupUI() {
dimView.backgroundColor = AppColor.overlayScrim
dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(cancelTapped)))
cardView.backgroundColor = .white
cardView.layer.cornerRadius = AppRadius.xl
cardView.clipsToBounds = true
titleLabel.text = "收款金额"
titleLabel.font = .systemFont(ofSize: 18, weight: .bold)
titleLabel.textColor = AppColor.textPrimary
amountContainer.backgroundColor = AppColor.inputBackground
amountContainer.layer.cornerRadius = AppRadius.sm
currencyLabel.text = "¥"
currencyLabel.font = .systemFont(ofSize: 20, weight: .bold)
currencyLabel.textColor = AppColor.textPrimary
amountField.placeholder = "请输入收款金额"
amountField.font = .systemFont(ofSize: 18, weight: .medium)
amountField.keyboardType = .decimalPad
amountField.borderStyle = .none
amountField.backgroundColor = .clear
amountField.addTarget(self, action: #selector(amountChanged), for: .editingChanged)
remarkTitleLabel.text = "备注 (选填)"
remarkTitleLabel.font = .systemFont(ofSize: 14)
remarkTitleLabel.textColor = UIColor(hex: 0x4B5563)
remarkField.font = .systemFont(ofSize: 14)
remarkField.textColor = AppColor.textPrimary
remarkField.backgroundColor = .white
remarkField.layer.borderColor = UIColor(hex: 0xE5E7EB).cgColor
remarkField.layer.borderWidth = 1
remarkField.layer.cornerRadius = AppRadius.sm
remarkField.textContainerInset = UIEdgeInsets(top: 10, left: 8, bottom: 10, right: 8)
remarkField.delegate = self
remarkCountLabel.font = .systemFont(ofSize: 12)
remarkCountLabel.textColor = AppColor.textTertiary
remarkCountLabel.textAlignment = .right
remarkCountLabel.text = "0/200"
buttonStack.axis = .horizontal
buttonStack.spacing = AppSpacing.sm
buttonStack.distribution = .fillEqually
cancelButton.setTitle("取消", for: .normal)
cancelButton.setTitleColor(AppColor.textSecondary, for: .normal)
cancelButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
cancelButton.backgroundColor = AppColor.inputBackground
cancelButton.layer.cornerRadius = AppRadius.sm
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
confirmButton.setTitle("确定", for: .normal)
confirmButton.setTitleColor(.white, for: .normal)
confirmButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
confirmButton.backgroundColor = AppColor.primary
confirmButton.layer.cornerRadius = AppRadius.sm
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
addSubview(dimView)
addSubview(cardView)
cardView.addSubview(titleLabel)
cardView.addSubview(amountContainer)
amountContainer.addSubview(currencyLabel)
amountContainer.addSubview(amountField)
cardView.addSubview(remarkTitleLabel)
cardView.addSubview(remarkField)
cardView.addSubview(remarkCountLabel)
cardView.addSubview(buttonStack)
buttonStack.addArrangedSubview(cancelButton)
buttonStack.addArrangedSubview(confirmButton)
dimView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
cardView.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview().inset(AppSpacing.md)
}
amountContainer.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(20)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(48)
}
currencyLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(AppSpacing.sm)
make.centerY.equalToSuperview()
}
amountField.snp.makeConstraints { make in
make.leading.equalTo(currencyLabel.snp.trailing).offset(AppSpacing.xs)
make.trailing.equalToSuperview().inset(AppSpacing.sm)
make.centerY.equalToSuperview()
}
remarkTitleLabel.snp.makeConstraints { make in
make.top.equalTo(amountContainer.snp.bottom).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
}
remarkField.snp.makeConstraints { make in
make.top.equalTo(remarkTitleLabel.snp.bottom).offset(AppSpacing.xs)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(100)
}
remarkCountLabel.snp.makeConstraints { make in
make.top.equalTo(remarkField.snp.bottom).offset(AppSpacing.xxs)
make.trailing.equalToSuperview().inset(AppSpacing.md)
}
buttonStack.snp.makeConstraints { make in
make.top.equalTo(remarkCountLabel.snp.bottom).offset(AppSpacing.lg)
make.leading.trailing.bottom.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(44)
}
}
@objc private func amountChanged() {
onAmountChange?(amountField.text ?? "")
}
@objc private func cancelTapped() {
onCancel?()
}
@objc private func confirmTapped() {
onConfirm?()
}
}
extension PaymentSetAmountDialogView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
onRemarkChange?(textView.text ?? "")
}
}