Files
suixinkan_uikit/suixinkan/UI/CooperationOrder/CooperationAcquirerViewController.swift
汉秋 5eef31b8da 完善景区排队设置页与全局按钮配置迁移。
重构排队设置与变更日志交互,补充 Overlay 与资源,并将多页面主操作按钮统一到 UIButton Configuration,同步更新相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 16:51:23 +08:00

256 lines
9.5 KiB
Swift
Raw Permalink 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.

//
// CooperationAcquirerViewController.swift
// suixinkan
//
import AVFoundation
import SnapKit
import UIKit
/// Android `CooperationAcquirerScreen`
final class CooperationAcquirerViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate {
private let viewModel = CooperationAcquirerViewModel()
private let orderAPI = NetworkServices.shared.orderAPI
private let tableView = UITableView(frame: .zero, style: .plain)
private let refreshControl = UIRefreshControl()
private let emptyLabel = UILabel()
private var shouldRefreshOnAppear = false
private var commissionDialogView: CooperationCommissionRateDialogView?
override func setupNavigationBar() {
title = "合作获客员"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "扫码绑定",
style: .plain,
target: self,
action: #selector(startScanBind)
)
navigationItem.rightBarButtonItem?.tintColor = AppColor.primary
}
override func setupUI() {
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 142
tableView.contentInset.top = AppSpacing.sm
tableView.verticalScrollIndicatorInsets.top = AppSpacing.sm
tableView.dataSource = self
tableView.delegate = self
tableView.register(CooperationAcquirerCell.self, forCellReuseIdentifier: CooperationAcquirerCell.reuseIdentifier)
tableView.refreshControl = refreshControl
emptyLabel.text = "暂无合作获客员"
emptyLabel.font = .systemFont(ofSize: 14)
emptyLabel.textColor = AppColor.textTertiary
emptyLabel.textAlignment = .center
emptyLabel.isHidden = true
view.addSubview(tableView)
view.addSubview(emptyLabel)
}
override func setupConstraints() {
tableView.snp.makeConstraints { make in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
emptyLabel.snp.makeConstraints { make in
make.center.equalTo(tableView)
}
}
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) }
}
viewModel.onNavigateBindAcquirer = { [weak self] saleUserId in
Task { @MainActor in self?.pushBindAcquirer(saleUserId: saleUserId) }
}
refreshControl.addTarget(self, action: #selector(refreshTriggered), for: .valueChanged)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard CooperationOrderFeature.hasPermission(in: AppStore.shared.permissions.permissionItems()) else {
showToast("暂无合作订单权限")
navigationController?.popViewController(animated: false)
return
}
if shouldRefreshOnAppear {
shouldRefreshOnAppear = false
Task { await viewModel.refresh(api: orderAPI) }
}
}
override func viewDidLoad() {
super.viewDidLoad()
Task {
showLoading()
defer { hideLoading() }
await viewModel.loadAcquirers(api: orderAPI, initial: true)
}
}
@objc private func startScanBind() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
presentScanner()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
Task { @MainActor [weak self] in
if granted {
self?.presentScanner()
}
}
}
default:
showToast("需要相机权限才能扫码")
}
}
@objc private func refreshTriggered() {
Task { await viewModel.refresh(api: orderAPI) }
}
private func presentScanner() {
let scanner = QRCodeScannerViewController()
scanner.onScanResult = { [weak self] result in
self?.viewModel.processScanResult(result)
}
let nav = UINavigationController(rootViewController: scanner)
nav.modalPresentationStyle = .fullScreen
present(nav, animated: true)
}
private func pushBindAcquirer(saleUserId: Int) {
let controller = BindAcquirerViewController(saleUserId: saleUserId)
controller.onBindSuccess = { [weak self] in
self?.shouldRefreshOnAppear = true
self?.navigationController?.popViewController(animated: true)
}
if presentedViewController != nil {
dismiss(animated: true) { [weak self] in
self?.navigationController?.pushViewController(controller, animated: true)
}
return
}
navigationController?.pushViewController(controller, animated: true)
}
private func applyViewModel() {
if viewModel.isRefreshing {
if !refreshControl.isRefreshing { refreshControl.beginRefreshing() }
} else {
refreshControl.endRefreshing()
}
emptyLabel.isHidden = !viewModel.acquirers.isEmpty || viewModel.isRefreshing
tableView.reloadData()
applyCommissionDialogState()
}
private func presentRemarkDialog(for acquirer: CooperativeSalerEntity) {
viewModel.startEditRemark(acquirer)
let alert = UIAlertController(title: "修改备注", message: "最多\(CooperationOrderFeature.remarkNameMaxLength)个字符", preferredStyle: .alert)
alert.addTextField { [weak self] field in
field.text = self?.viewModel.remarkDraft
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel) { [weak self] _ in
self?.viewModel.dismissRemarkDialog()
})
alert.addAction(UIAlertAction(title: "保存", style: .default) { [weak self] _ in
guard let self else { return }
if let text = alert.textFields?.first?.text {
self.viewModel.updateRemarkDraft(text)
}
Task { await self.viewModel.saveRemark(api: self.orderAPI) }
})
present(alert, animated: true)
}
private func presentCommissionDialog(for acquirer: CooperativeSalerEntity) {
viewModel.startEditCommissionRate(acquirer)
commissionDialogView?.dismiss()
let dialog = CooperationCommissionRateDialogView()
dialog.onCommissionRateChange = { [weak self] text in
self?.viewModel.updateCommissionRateDraft(text)
}
dialog.onSmsCodeChange = { [weak self] text in
self?.viewModel.updateCommissionSmsCodeDraft(text)
}
dialog.onSendSmsCode = { [weak self, weak dialog] in
guard let self else { return }
if let dialog {
self.syncCommissionDialogDrafts(dialog)
}
Task { await self.viewModel.requestCommissionSmsCode(api: self.orderAPI) }
}
dialog.onCancel = { [weak self] in
self?.viewModel.dismissCommissionRateDialog()
}
dialog.onConfirm = { [weak self, weak dialog] in
guard let self else { return }
if let dialog {
self.syncCommissionDialogDrafts(dialog)
}
Task { await self.viewModel.saveCommissionRate(api: self.orderAPI) }
}
commissionDialogView = dialog
applyCommissionDialogState()
dialog.show(in: view)
}
private func syncCommissionDialogDrafts(_ dialog: CooperationCommissionRateDialogView) {
viewModel.updateCommissionRateDraft(dialog.currentCommissionRate)
viewModel.updateCommissionSmsCodeDraft(dialog.currentSmsCode)
}
private func applyCommissionDialogState() {
guard let dialog = commissionDialogView else { return }
guard let acquirer = viewModel.commissionEditingAcquirer else {
dialog.dismiss()
commissionDialogView = nil
return
}
let phone = acquirer.salerPhone.isEmpty ? acquirer.displayPhone : acquirer.salerPhone
dialog.apply(
commissionRate: viewModel.commissionRateDraft,
smsCode: viewModel.commissionSmsCodeDraft,
phone: CooperationOrderPhoneMask.mask(phone),
countdown: viewModel.commissionSmsCountdown
)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.acquirers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(
withIdentifier: CooperationAcquirerCell.reuseIdentifier,
for: indexPath
) as? CooperationAcquirerCell else {
return UITableViewCell()
}
let acquirer = viewModel.acquirers[indexPath.row]
cell.configure(with: acquirer)
cell.onEditRemark = { [weak self] in
self?.presentRemarkDialog(for: acquirer)
}
cell.onEditCommission = { [weak self] in
self?.presentCommissionDialog(for: acquirer)
}
cell.onCall = {
let phone = acquirer.displayPhone.trimmingCharacters(in: .whitespacesAndNewlines)
guard !phone.isEmpty, let url = URL(string: "tel://\(phone)") else { return }
UIApplication.shared.open(url)
}
return cell
}
}