// // 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 } }