将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
256 lines
9.5 KiB
Swift
256 lines
9.5 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|