Files
suixinkan_uikit/suixinkan/UI/CooperationOrder/CooperationAcquirerViewController.swift
汉秋 005349f8e6 模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 11:01:08 +08:00

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

//
// 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) { [weak self] granted in
Task { @MainActor 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
}
}