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

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

239 lines
9.1 KiB
Swift

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