Add scenic selection and cooperation order flows aligned with Android.

Upgrade scenic picker with search, location sorting, permission apply/status, and blocking home dialog; add cooperation order module and order source picker improvements with unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 10:48:43 +08:00
parent 3c4ca7eefb
commit 2bea05b1d9
50 changed files with 5603 additions and 191 deletions

View File

@ -0,0 +1,170 @@
//
// 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 emptyLabel = UILabel()
private var isApplyingTab = false
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.dataSource = self
tableView.delegate = self
tableView.register(ReferralLeadCell.self, forCellReuseIdentifier: ReferralLeadCell.reuseIdentifier)
tableView.register(AcquisitionOrderCell.self, forCellReuseIdentifier: AcquisitionOrderCell.reuseIdentifier)
tableView.refreshControl = refreshControl
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(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()
}
tableView.snp.makeConstraints { make in
make.top.equalTo(searchBar.snp.bottom)
make.leading.trailing.bottom.equalToSuperview()
}
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.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)
refreshControl.endRefreshing()
}
}
private func applyViewModel() {
if !isApplyingTab {
isApplyingTab = true
tabBarView.setSelectedTab(viewModel.selectedTab)
isApplyingTab = false
}
searchBar.isHidden = viewModel.selectedTab != 1
searchBar.textField.text = viewModel.searchText
let isEmpty = viewModel.selectedTab == 0 ? viewModel.leads.isEmpty : viewModel.orders.isEmpty
emptyLabel.isHidden = !isEmpty || viewModel.isRefreshing
tableView.reloadData()
}
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) }
}
}