// // CooperationOrderListViewController.swift // suixinkan // import SnapKit import UIKit /// 合作订单列表页,对齐 Android `CooperationOrderListScreen`。 final class CooperationOrderListViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate { private let viewModel = CooperationOrderListViewModel() private let orderAPI = NetworkServices.shared.orderAPI private let tabBarView = CooperationOrderTabBar() private let searchBar = CooperationOrderSearchBar() private let tableView = UITableView(frame: .zero, style: .plain) private let refreshControl = UIRefreshControl() private let loadingIndicator = UIActivityIndicatorView(style: .medium) private let footerSpinner = UIActivityIndicatorView(style: .medium) private let footerLabel = UILabel() private let emptyLabel = UILabel() private var isApplyingTab = false private var isSearchBarVisible = false private var searchBarHeightConstraint: Constraint? override func setupNavigationBar() { title = "合作订单" navigationItem.rightBarButtonItem = UIBarButtonItem( title: "合作获客员", style: .plain, target: self, action: #selector(openAcquirer) ) navigationItem.rightBarButtonItem?.tintColor = AppColor.primary } override func setupUI() { view.backgroundColor = AppColor.pageBackground searchBar.isHidden = true tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.contentInset.top = AppSpacing.sm tableView.verticalScrollIndicatorInsets.top = AppSpacing.sm tableView.dataSource = self tableView.delegate = self tableView.register(ReferralLeadCell.self, forCellReuseIdentifier: ReferralLeadCell.reuseIdentifier) tableView.register(AcquisitionOrderCell.self, forCellReuseIdentifier: AcquisitionOrderCell.reuseIdentifier) tableView.refreshControl = refreshControl loadingIndicator.hidesWhenStopped = true loadingIndicator.color = AppColor.primary footerSpinner.hidesWhenStopped = true footerSpinner.color = AppColor.primary footerLabel.font = .systemFont(ofSize: 14) footerLabel.textColor = AppColor.textTertiary footerLabel.textAlignment = .center footerLabel.text = "没有更多" emptyLabel.text = "暂无相关合作订单" emptyLabel.font = .systemFont(ofSize: 14) emptyLabel.textColor = AppColor.textTertiary emptyLabel.textAlignment = .center emptyLabel.isHidden = true view.addSubview(tabBarView) view.addSubview(searchBar) view.addSubview(tableView) view.addSubview(loadingIndicator) view.addSubview(emptyLabel) } override func setupConstraints() { tabBarView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.leading.trailing.equalToSuperview() } searchBar.snp.makeConstraints { make in make.top.equalTo(tabBarView.snp.bottom) make.leading.trailing.equalToSuperview() searchBarHeightConstraint = make.height.equalTo(0).constraint } tableView.snp.makeConstraints { make in make.top.equalTo(searchBar.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } loadingIndicator.snp.makeConstraints { make in make.center.equalTo(tableView) } 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) } } tabBarView.onTabSelected = { [weak self] index in guard let self else { return } self.viewModel.onTabSelected(index) Task { await self.viewModel.refreshCurrentTab(api: self.orderAPI) } } searchBar.onSearchTextChange = { [weak self] text in self?.viewModel.onSearchTextChange(text) } searchBar.onSearch = { [weak self] in guard let self else { return } Task { await self.viewModel.refreshAcquisitionOrders(api: self.orderAPI) } } 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 } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { await viewModel.refreshCurrentTab(api: orderAPI) } } @objc private func openAcquirer() { navigationController?.pushViewController(CooperationAcquirerViewController(), animated: true) } @objc private func refreshTriggered() { Task { await viewModel.refreshCurrentTab(api: orderAPI) } } private func applyViewModel() { if !isApplyingTab { isApplyingTab = true tabBarView.setSelectedTab(viewModel.selectedTab) isApplyingTab = false } updateSearchBarVisibility(viewModel.selectedTab == 1) searchBar.textField.text = viewModel.searchText let isEmpty = viewModel.selectedTab == 0 ? viewModel.leads.isEmpty : viewModel.orders.isEmpty let isLoading = viewModel.isRefreshing && isEmpty if viewModel.isRefreshing { if !refreshControl.isRefreshing { refreshControl.beginRefreshing() } } else { refreshControl.endRefreshing() } if isLoading { loadingIndicator.startAnimating() emptyLabel.isHidden = true tableView.tableFooterView = nil } else { loadingIndicator.stopAnimating() emptyLabel.isHidden = !isEmpty tableView.tableFooterView = tableFooterView(isEmpty: isEmpty) } tableView.reloadData() } private func updateSearchBarVisibility(_ isVisible: Bool) { guard isSearchBarVisible != isVisible else { return } isSearchBarVisible = isVisible searchBarHeightConstraint?.update(offset: isVisible ? 62 : 0) if isVisible { searchBar.isHidden = false } else { searchBar.textField.resignFirstResponder() } UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseInOut]) { self.view.layoutIfNeeded() } completion: { _ in if self.isSearchBarVisible == isVisible { self.searchBar.isHidden = !isVisible } } } private func tableFooterView(isEmpty: Bool) -> UIView? { guard !isEmpty else { return nil } if viewModel.isLoadingMore { let container = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 44)) footerSpinner.center = CGPoint(x: container.bounds.midX, y: container.bounds.midY) footerSpinner.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin] container.addSubview(footerSpinner) footerSpinner.startAnimating() return container } footerSpinner.stopAnimating() guard !viewModel.canLoadMore else { return nil } footerLabel.frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 44) return footerLabel } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { viewModel.selectedTab == 0 ? viewModel.leads.count : viewModel.orders.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if viewModel.selectedTab == 0 { guard let cell = tableView.dequeueReusableCell( withIdentifier: ReferralLeadCell.reuseIdentifier, for: indexPath ) as? ReferralLeadCell else { return UITableViewCell() } cell.configure(with: viewModel.leads[indexPath.row]) cell.onImageTap = { [weak self] index, urls in let preview = ImagePreviewViewController(imageURLs: urls, startIndex: index) self?.present(preview, animated: true) } return cell } guard let cell = tableView.dequeueReusableCell( withIdentifier: AcquisitionOrderCell.reuseIdentifier, for: indexPath ) as? AcquisitionOrderCell else { return UITableViewCell() } cell.configure(with: viewModel.orders[indexPath.row]) return cell } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard viewModel.shouldLoadMore(at: indexPath.row) else { return } Task { await viewModel.loadMore(api: orderAPI) } } }