Files
suixinkan_ios_uikit/suixinkan_ios/Features/Profile/ViewControllers/RealNameAuthViewController.swift
2026-06-26 14:33:31 +08:00

262 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: UIViewController {
private let viewModel = RealNameAuthViewModel()
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 statusLabel = UILabel()
private let longValidSwitch = UISwitch()
private let startDatePicker = UIDatePicker()
private let endDatePicker = UIDatePicker()
override func viewDidLoad() {
super.viewDidLoad()
title = "实名认证"
view.backgroundColor = AppDesignUIKit.pageBackground
setupForm()
viewModel.onChange = { [weak self] in self?.applyViewModel() }
Task { await loadInfo() }
}
private func setupForm() {
contentStack.axis = .vertical
contentStack.spacing = 16
view.addSubview(scrollView)
scrollView.addSubview(contentStack)
scrollView.snp.makeConstraints { make in make.edges.equalToSuperview() }
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
make.width.equalTo(scrollView).offset(-32)
}
[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)
let submitButton = makePrimaryButton(title: "下一步")
submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside)
statusLabel.numberOfLines = 0
statusLabel.font = .systemFont(ofSize: 13)
statusLabel.textColor = AppDesignUIKit.textSecondary
contentStack.addArrangedSubview(makeSectionTitle("审核状态"))
contentStack.addArrangedSubview(statusLabel)
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
longValidSwitch.addTarget(self, action: #selector(longValidChanged), for: .valueChanged)
contentStack.addArrangedSubview(longValidRow)
contentStack.addArrangedSubview(startDatePicker)
contentStack.addArrangedSubview(endDatePicker)
let smsRow = UIStackView(arrangedSubviews: [smsCodeField, sendCodeButton])
smsRow.axis = .horizontal
smsRow.spacing = 8
smsCodeField.snp.makeConstraints { make in make.height.equalTo(44) }
contentStack.addArrangedSubview(smsRow)
contentStack.addArrangedSubview(submitButton)
}
private func makeSectionTitle(_ text: String) -> UILabel {
let label = UILabel()
label.text = text
label.font = .systemFont(ofSize: 17, weight: .semibold)
return label
}
private func labeledField(_ title: String, _ field: UITextField) -> UIStackView {
let label = UILabel()
label.text = title
label.font = .systemFont(ofSize: 15, weight: .medium)
let stack = UIStackView(arrangedSubviews: [label, field])
stack.axis = .vertical
stack.spacing = 8
field.snp.makeConstraints { make in make.height.equalTo(44) }
return stack
}
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
if let data = viewModel.pendingFrontImageData, let image = UIImage(data: data) {
frontImageView.image = image
} else if !viewModel.frontUrl.isEmpty {
frontImageView.loadRemoteImage(urlString: viewModel.frontUrl, contentMode: .scaleAspectFit)
}
if let data = viewModel.pendingBackImageData, let image = UIImage(data: data) {
backImageView.image = image
} else if !viewModel.backUrl.isEmpty {
backImageView.image = nil
backImageView.loadRemoteImage(urlString: viewModel.backUrl, contentMode: .scaleAspectFit)
}
if let info = viewModel.info {
statusLabel.text = """
审核状态:\(viewModel.auditStatusText(info.auditStatus))
审核人:\(info.auditor?.name ?? "--")
审核时间:\(info.auditAt ?? "--")
审核备注:\(info.rejectReason ?? "--")
"""
} else {
statusLabel.text = "尚未提交实名认证"
}
if let message = viewModel.statusMessage {
showToast(message)
}
}
private func loadInfo() async {
do {
try await appServices.globalLoading.withLoading(message: "加载中...") {
try await self.viewModel.load(api: self.appServices.profileAPI)
}
} catch {
showToast(error.localizedDescription)
}
}
@objc private func longValidChanged() {
viewModel.isLongValid = longValidSwitch.isOn
}
@objc private func sendCodeTapped() {
Task {
do {
try await viewModel.sendCode(api: appServices.profileAPI)
} catch {
showToast(error.localizedDescription)
}
}
}
@objc private func submitTapped() {
viewModel.realName = realNameField.text ?? ""
viewModel.idCardNo = idCardField.text ?? ""
viewModel.smsCode = smsCodeField.text ?? ""
viewModel.startDate = startDatePicker.date
viewModel.endDate = endDatePicker.date
guard let scenicId = appServices.accountContext.currentScenic?.id else {
showToast("请先选择景区")
return
}
Task {
do {
try await appServices.globalLoading.withLoading(message: "提交中...") {
try await self.viewModel.submit(
api: self.appServices.profileAPI,
uploader: self.appServices.ossUploadService,
scenicId: scenicId
)
}
} catch {
showToast(error.localizedDescription)
}
}
}
private var pendingSide: RealNameImageSide = .front
@objc private func pickFront() {
pendingSide = .front
presentImagePicker()
}
@objc private func pickBack() {
pendingSide = .back
presentImagePicker()
}
private func presentImagePicker() {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = 1
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true)
}
}
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 }
DispatchQueue.main.async {
do {
try self.viewModel.prepareIdentityImage(data: data, side: self.pendingSide)
} catch {
self.showToast(error.localizedDescription)
}
}
}
}
}
private extension UILabel {
convenience init(text: String) {
self.init()
self.text = text
self.font = .systemFont(ofSize: 16, weight: .semibold)
}
}