295 lines
12 KiB
Swift
295 lines
12 KiB
Swift
//
|
||
// WithdrawalSettingsViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import PhotosUI
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 提现设置页面,两步表单绑定银行卡。
|
||
final class WithdrawalSettingsViewController: BaseViewController {
|
||
|
||
private let viewModel = WithdrawalSettingsViewModel()
|
||
private let profileAPI = NetworkServices.shared.profileAPI
|
||
private let ossUploadService = NetworkServices.shared.ossUploadService
|
||
|
||
private let scrollView = UIScrollView()
|
||
private let contentStack = UIStackView()
|
||
private let stepLabel = UILabel()
|
||
private let holderNameField = UITextField()
|
||
private let bankNoField = UITextField()
|
||
private let frontImageView = UIImageView()
|
||
private let backImageView = UIImageView()
|
||
private let bankNameField = UITextField()
|
||
private let provinceButton = UIButton(type: .system)
|
||
private let cityButton = UIButton(type: .system)
|
||
private let branchField = UITextField()
|
||
private let smsCodeField = UITextField()
|
||
private let agreeSwitch = UISwitch()
|
||
private let actionButton = AppButton(title: "下一步")
|
||
|
||
private var pickingSide: BankCardImageSide = .front
|
||
private var stepTwoStack = UIStackView()
|
||
|
||
override func setupNavigationBar() {
|
||
title = "提现设置"
|
||
}
|
||
|
||
override func setupUI() {
|
||
view.backgroundColor = UIColor(hex: 0xF7FAFF)
|
||
contentStack.axis = .vertical
|
||
contentStack.spacing = 16
|
||
|
||
stepLabel.font = .systemFont(ofSize: 14, weight: .medium)
|
||
stepLabel.textColor = AppColor.text666
|
||
|
||
[holderNameField, bankNoField, bankNameField, branchField, smsCodeField].forEach {
|
||
$0.borderStyle = .roundedRect
|
||
$0.font = .systemFont(ofSize: 16)
|
||
}
|
||
holderNameField.placeholder = "持卡人姓名"
|
||
bankNoField.placeholder = "银行卡号"
|
||
bankNoField.keyboardType = .numberPad
|
||
bankNameField.placeholder = "开户银行"
|
||
branchField.placeholder = "支行名称"
|
||
smsCodeField.placeholder = "短信验证码"
|
||
smsCodeField.keyboardType = .numberPad
|
||
|
||
provinceButton.contentHorizontalAlignment = .leading
|
||
cityButton.contentHorizontalAlignment = .leading
|
||
provinceButton.setTitle("请选择省份", for: .normal)
|
||
cityButton.setTitle("请选择城市", for: .normal)
|
||
|
||
[frontImageView, backImageView].forEach {
|
||
$0.backgroundColor = UIColor(hex: 0xF8FAFC)
|
||
$0.contentMode = .scaleAspectFit
|
||
$0.layer.cornerRadius = 8
|
||
$0.clipsToBounds = true
|
||
$0.isUserInteractionEnabled = true
|
||
$0.snp.makeConstraints { make in make.height.equalTo(140) }
|
||
}
|
||
frontImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(pickFront)))
|
||
backImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(pickBack)))
|
||
|
||
stepTwoStack.axis = .vertical
|
||
stepTwoStack.spacing = 16
|
||
stepTwoStack.isHidden = true
|
||
stepTwoStack.addArrangedSubview(labeled("开户银行", bankNameField))
|
||
stepTwoStack.addArrangedSubview(labeled("所在省份", provinceButton))
|
||
stepTwoStack.addArrangedSubview(labeled("所在城市", cityButton))
|
||
stepTwoStack.addArrangedSubview(labeled("支行名称", branchField))
|
||
|
||
let smsRow = UIStackView(arrangedSubviews: [smsCodeField, sendCodeButton()])
|
||
smsRow.axis = .horizontal
|
||
smsRow.spacing = 8
|
||
stepTwoStack.addArrangedSubview(labeled("短信验证码", smsRow))
|
||
|
||
view.addSubview(scrollView)
|
||
scrollView.addSubview(contentStack)
|
||
view.addSubview(actionButton)
|
||
|
||
contentStack.addArrangedSubview(stepLabel)
|
||
contentStack.addArrangedSubview(labeled("持卡人姓名", holderNameField))
|
||
contentStack.addArrangedSubview(labeled("银行卡号", bankNoField))
|
||
contentStack.addArrangedSubview(labeled("银行卡正面", frontImageView))
|
||
contentStack.addArrangedSubview(labeled("银行卡反面", backImageView))
|
||
contentStack.addArrangedSubview(agreeRow())
|
||
contentStack.addArrangedSubview(stepTwoStack)
|
||
}
|
||
|
||
override func setupConstraints() {
|
||
scrollView.snp.makeConstraints { make in
|
||
make.top.leading.trailing.equalToSuperview()
|
||
make.bottom.equalTo(actionButton.snp.top).offset(-12)
|
||
}
|
||
contentStack.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview().inset(16)
|
||
make.width.equalTo(scrollView).offset(-32)
|
||
}
|
||
actionButton.snp.makeConstraints { make in
|
||
make.leading.trailing.equalToSuperview().inset(16)
|
||
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(14)
|
||
}
|
||
}
|
||
|
||
override func bindActions() {
|
||
viewModel.onStateChange = { [weak self] in self?.applyViewModel() }
|
||
actionButton.addTarget(self, action: #selector(actionTapped), for: .touchUpInside)
|
||
provinceButton.addTarget(self, action: #selector(selectProvince), for: .touchUpInside)
|
||
cityButton.addTarget(self, action: #selector(selectCity), for: .touchUpInside)
|
||
agreeSwitch.addTarget(self, action: #selector(agreeChanged), for: .valueChanged)
|
||
holderNameField.addTarget(self, action: #selector(fieldsChanged), for: .editingChanged)
|
||
bankNoField.addTarget(self, action: #selector(fieldsChanged), for: .editingChanged)
|
||
bankNameField.addTarget(self, action: #selector(fieldsChanged), for: .editingChanged)
|
||
branchField.addTarget(self, action: #selector(fieldsChanged), for: .editingChanged)
|
||
smsCodeField.addTarget(self, action: #selector(fieldsChanged), for: .editingChanged)
|
||
Task { await loadData() }
|
||
}
|
||
|
||
private func applyViewModel() {
|
||
stepLabel.text = viewModel.currentStep == 1 ? "步骤 1/2:填写银行卡信息" : "步骤 2/2:完善开户信息"
|
||
holderNameField.text = viewModel.holderName
|
||
bankNoField.text = viewModel.bankNo
|
||
bankNameField.text = viewModel.bankName
|
||
branchField.text = viewModel.branchName
|
||
smsCodeField.text = viewModel.verificationCode
|
||
agreeSwitch.isOn = viewModel.isAgree
|
||
stepTwoStack.isHidden = viewModel.currentStep == 1
|
||
actionButton.setTitle(viewModel.currentStep == 1 ? "下一步" : "提交", for: .normal)
|
||
actionButton.isEnabled = viewModel.currentStep == 1 ? viewModel.canGoNextStep : viewModel.canSubmit
|
||
|
||
provinceButton.setTitle(viewModel.provinceName.isEmpty ? "请选择省份" : viewModel.provinceName, for: .normal)
|
||
cityButton.setTitle(viewModel.cityName.isEmpty ? "请选择城市" : viewModel.cityName, for: .normal)
|
||
|
||
updateImageView(frontImageView, pending: viewModel.pendingFrontImageData, url: viewModel.bankCardFrontURL)
|
||
updateImageView(backImageView, pending: viewModel.pendingBackImageData, url: viewModel.bankCardBackURL)
|
||
}
|
||
|
||
private func updateImageView(_ imageView: UIImageView, pending: Data?, url: String) {
|
||
if let pending, let image = UIImage(data: pending) {
|
||
imageView.image = image
|
||
} else if url != "pending" {
|
||
imageView.loadRemoteImage(urlString: url)
|
||
}
|
||
}
|
||
|
||
@objc private func fieldsChanged() {
|
||
viewModel.updateHolderName(holderNameField.text ?? "")
|
||
viewModel.updateBankNo(bankNoField.text ?? "")
|
||
viewModel.updateBankName(bankNameField.text ?? "")
|
||
viewModel.updateBranchName(branchField.text ?? "")
|
||
viewModel.updateVerificationCode(smsCodeField.text ?? "")
|
||
}
|
||
|
||
@objc private func agreeChanged() {
|
||
viewModel.updateAgree(agreeSwitch.isOn)
|
||
}
|
||
|
||
@objc private func actionTapped() {
|
||
if viewModel.currentStep == 1 {
|
||
viewModel.goNextStep()
|
||
} else {
|
||
Task { await submit() }
|
||
}
|
||
}
|
||
|
||
@objc private func pickFront() { pickImage(side: .front) }
|
||
@objc private func pickBack() { pickImage(side: .back) }
|
||
|
||
private func pickImage(side: BankCardImageSide) {
|
||
pickingSide = side
|
||
var configuration = PHPickerConfiguration(photoLibrary: .shared())
|
||
configuration.filter = .images
|
||
configuration.selectionLimit = 1
|
||
let picker = PHPickerViewController(configuration: configuration)
|
||
picker.delegate = self
|
||
present(picker, animated: true)
|
||
}
|
||
|
||
@objc private func selectProvince() {
|
||
presentAreaPicker(title: "选择省份", options: viewModel.provinceOptions) { [weak self] area in
|
||
self?.viewModel.selectProvince(area)
|
||
}
|
||
}
|
||
|
||
@objc private func selectCity() {
|
||
presentAreaPicker(title: "选择城市", options: viewModel.cityOptions) { [weak self] area in
|
||
self?.viewModel.selectCity(area)
|
||
}
|
||
}
|
||
|
||
private func presentAreaPicker(title: String, options: [AreasResponse], onSelect: @escaping (AreasResponse) -> Void) {
|
||
let alert = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
|
||
options.forEach { area in
|
||
alert.addAction(UIAlertAction(title: area.name, style: .default) { _ in
|
||
onSelect(area)
|
||
})
|
||
}
|
||
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
|
||
present(alert, animated: true)
|
||
}
|
||
|
||
private func loadData() async {
|
||
showLoading()
|
||
defer { hideLoading() }
|
||
do {
|
||
try await viewModel.load(api: profileAPI)
|
||
} catch {
|
||
showToast(error.localizedDescription)
|
||
}
|
||
}
|
||
|
||
private func submit() async {
|
||
let scenicId = AppStore.shared.currentScenicId
|
||
guard scenicId > 0 else {
|
||
showToast("当前景区信息缺失")
|
||
return
|
||
}
|
||
showLoading()
|
||
defer { hideLoading() }
|
||
do {
|
||
try await viewModel.submit(api: profileAPI, uploader: ossUploadService, scenicId: scenicId)
|
||
showToast("提交成功")
|
||
navigationController?.popViewController(animated: true)
|
||
} catch {
|
||
showToast(error.localizedDescription)
|
||
}
|
||
}
|
||
|
||
private func labeled(_ title: String, _ view: UIView) -> UIStackView {
|
||
let label = UILabel()
|
||
label.text = title
|
||
label.font = .systemFont(ofSize: 14)
|
||
let stack = UIStackView(arrangedSubviews: [label, view])
|
||
stack.axis = .vertical
|
||
stack.spacing = 8
|
||
return stack
|
||
}
|
||
|
||
private func agreeRow() -> UIStackView {
|
||
let label = UILabel()
|
||
label.text = "我已阅读并同意相关协议"
|
||
label.font = .systemFont(ofSize: 14)
|
||
let row = UIStackView(arrangedSubviews: [label, agreeSwitch])
|
||
row.axis = .horizontal
|
||
return row
|
||
}
|
||
|
||
private func sendCodeButton() -> UIButton {
|
||
let button = UIButton(type: .system)
|
||
button.setTitle("获取验证码", for: .normal)
|
||
button.addAction(UIAction { [weak self] _ in
|
||
Task {
|
||
guard let self else { return }
|
||
self.showLoading()
|
||
defer { self.hideLoading() }
|
||
do {
|
||
try await self.viewModel.sendCode(api: self.profileAPI)
|
||
self.showToast("验证码已发送")
|
||
} catch {
|
||
self.showToast(error.localizedDescription)
|
||
}
|
||
}
|
||
}, for: .touchUpInside)
|
||
return button
|
||
}
|
||
}
|
||
|
||
extension WithdrawalSettingsViewController: PHPickerViewControllerDelegate {
|
||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||
picker.dismiss(animated: true)
|
||
guard let provider = results.first?.itemProvider, provider.canLoadObject(ofClass: UIImage.self) else { return }
|
||
provider.loadObject(ofClass: UIImage.self) { [weak self] object, _ in
|
||
guard let self, let image = object as? UIImage, let data = image.jpegData(compressionQuality: 0.9) else { return }
|
||
Task { @MainActor in
|
||
do {
|
||
try self.viewModel.prepareBankCardImage(data: data, side: self.pickingSide)
|
||
} catch {
|
||
self.showToast(error.localizedDescription)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|