Files
suixinkan_uikit/suixinkan/UI/Profile/RealNameAuthViewController.swift

256 lines
9.7 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.

//
// RealNameAuthViewController.swift
// suixinkan
//
import PhotosUI
import SnapKit
import UIKit
///
final class RealNameAuthViewController: BaseViewController {
private let viewModel = RealNameAuthViewModel()
private let profileAPI = NetworkServices.shared.profileAPI
private let ossUploadService = NetworkServices.shared.ossUploadService
private let scrollView = UIScrollView()
private let contentStack = UIStackView()
private let realNameField = UITextField()
private let idCardField = UITextField()
private let smsCodeField = UITextField()
private let frontImageView = UIImageView()
private let backImageView = UIImageView()
private let longValidSwitch = UISwitch()
private let startDatePicker = UIDatePicker()
private let endDatePicker = UIDatePicker()
private let submitButton = AppButton(title: "提交审核")
override func setupNavigationBar() {
title = "实名认证"
}
override func setupUI() {
view.backgroundColor = UIColor(hex: 0xF7FAFF)
contentStack.axis = .vertical
contentStack.spacing = 16
view.addSubview(scrollView)
scrollView.addSubview(contentStack)
view.addSubview(submitButton)
[realNameField, idCardField, smsCodeField].forEach {
$0.borderStyle = .roundedRect
$0.font = .systemFont(ofSize: 16)
}
realNameField.placeholder = "请输入姓名"
idCardField.placeholder = "请输入身份证号码"
smsCodeField.placeholder = "请输入短信验证码"
smsCodeField.keyboardType = .numberPad
[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(160) }
}
frontImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(pickFront)))
backImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(pickBack)))
startDatePicker.datePickerMode = .date
endDatePicker.datePickerMode = .date
if #available(iOS 17.0, *) {
startDatePicker.preferredDatePickerStyle = .compact
endDatePicker.preferredDatePickerStyle = .compact
}
let sendCodeButton = UIButton(type: .system)
sendCodeButton.setTitle("获取验证码", for: .normal)
sendCodeButton.addTarget(self, action: #selector(sendCodeTapped), for: .touchUpInside)
contentStack.addArrangedSubview(makeSectionTitle("身份证国徽面"))
contentStack.addArrangedSubview(backImageView)
contentStack.addArrangedSubview(makeSectionTitle("身份证人像面"))
contentStack.addArrangedSubview(frontImageView)
contentStack.addArrangedSubview(labeledField("姓名", realNameField))
contentStack.addArrangedSubview(labeledField("身份证号码", idCardField))
let longValidRow = UIStackView(arrangedSubviews: [UILabel(text: "长期有效"), longValidSwitch])
longValidRow.axis = .horizontal
contentStack.addArrangedSubview(longValidRow)
contentStack.addArrangedSubview(labeledField("起始日期", startDatePicker))
contentStack.addArrangedSubview(labeledField("结束日期", endDatePicker))
let smsRow = UIStackView(arrangedSubviews: [smsCodeField, sendCodeButton])
smsRow.axis = .horizontal
smsRow.spacing = 8
smsCodeField.snp.makeConstraints { make in make.width.equalTo(200) }
contentStack.addArrangedSubview(labeledField("短信验证码", smsRow))
}
override func setupConstraints() {
scrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
make.bottom.equalTo(submitButton.snp.top).offset(-12)
}
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
make.width.equalTo(scrollView).offset(-32)
}
submitButton.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() }
submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside)
longValidSwitch.addTarget(self, action: #selector(longValidChanged), for: .valueChanged)
realNameField.addTarget(self, action: #selector(fieldChanged), for: .editingChanged)
idCardField.addTarget(self, action: #selector(fieldChanged), for: .editingChanged)
smsCodeField.addTarget(self, action: #selector(fieldChanged), for: .editingChanged)
startDatePicker.addTarget(self, action: #selector(dateChanged), for: .valueChanged)
endDatePicker.addTarget(self, action: #selector(dateChanged), for: .valueChanged)
Task { await loadInfo() }
}
private func applyViewModel() {
realNameField.text = viewModel.realName
idCardField.text = viewModel.idCardNo
smsCodeField.text = viewModel.smsCode
longValidSwitch.isOn = viewModel.isLongValid
startDatePicker.date = viewModel.startDate
endDatePicker.date = viewModel.endDate
endDatePicker.isEnabled = !viewModel.isLongValid
submitButton.isEnabled = !viewModel.submitting
if let data = viewModel.pendingFrontImageData, let image = UIImage(data: data) {
frontImageView.image = image
} else {
frontImageView.loadRemoteImage(urlString: viewModel.frontUrl)
}
if let data = viewModel.pendingBackImageData, let image = UIImage(data: data) {
backImageView.image = image
} else {
backImageView.loadRemoteImage(urlString: viewModel.backUrl)
}
}
@objc private func fieldChanged() {
viewModel.updateRealName(realNameField.text ?? "")
viewModel.updateIdCardNo(idCardField.text ?? "")
viewModel.updateSmsCode(smsCodeField.text ?? "")
}
@objc private func dateChanged() {
viewModel.updateStartDate(startDatePicker.date)
viewModel.updateEndDate(endDatePicker.date)
}
@objc private func longValidChanged() {
viewModel.updateLongValid(longValidSwitch.isOn)
}
@objc private func sendCodeTapped() {
Task {
showLoading()
defer { hideLoading() }
do {
try await viewModel.sendCode(api: profileAPI)
showToast(viewModel.statusMessage ?? "验证码已发送")
} catch {
showToast(error.localizedDescription)
}
}
}
@objc private func submitTapped() {
Task { await submit() }
}
@objc private func pickFront() { pickImage(side: .front) }
@objc private func pickBack() { pickImage(side: .back) }
private var pickingSide: RealNameImageSide = .front
private func pickImage(side: RealNameImageSide) {
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)
}
private func loadInfo() 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 makeSectionTitle(_ text: String) -> UILabel {
let label = UILabel()
label.text = text
label.font = .systemFont(ofSize: 15, weight: .semibold)
return label
}
private func labeledField(_ title: String, _ field: UIView) -> UIStackView {
let titleLabel = UILabel(text: title)
titleLabel.font = .systemFont(ofSize: 14)
let stack = UIStackView(arrangedSubviews: [titleLabel, field])
stack.axis = .vertical
stack.spacing = 8
return stack
}
}
extension RealNameAuthViewController: 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.prepareIdentityImage(data: data, side: self.pickingSide)
} catch {
self.showToast(error.localizedDescription)
}
}
}
}
}
private extension UILabel {
convenience init(text: String) {
self.init()
self.text = text
}
}