添加景区选择与合作订单流程并对齐 Android
This commit is contained in:
@ -6,23 +6,58 @@
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 订单来源选择器。
|
||||
/// 订单来源选择 Bottom Sheet,对齐 Android `OrderSourcePicker` 弹层。
|
||||
final class OrderSourcePickerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
var onPersonSelected: ((OrderSourcePerson) -> Void)?
|
||||
var onLeadSelected: ((OrderSourceSelection) -> Void)?
|
||||
private enum MenuLevel {
|
||||
case person
|
||||
case lead
|
||||
}
|
||||
|
||||
private let people: [OrderSourcePerson]
|
||||
private let leads: [OrderSourceLead]
|
||||
private let selectedPerson: OrderSourcePerson?
|
||||
private let tableView = UITableView(frame: .zero, style: .insetGrouped)
|
||||
private var showingLeads = false
|
||||
private let orderNumber: String
|
||||
private let viewModel: OrderListViewModel
|
||||
private let onPersonSelected: (OrderSourcePerson) -> Void
|
||||
private let onLeadBound: (OrderSourceSelection) -> Void
|
||||
private let onCallPhone: (String) -> Void
|
||||
private let onPreviewImages: ([String], Int) -> Void
|
||||
|
||||
init(people: [OrderSourcePerson], leads: [OrderSourceLead], selectedPerson: OrderSourcePerson?) {
|
||||
self.people = people
|
||||
self.leads = leads
|
||||
self.selectedPerson = selectedPerson
|
||||
self.showingLeads = !leads.isEmpty
|
||||
private var level: MenuLevel = .person
|
||||
private var pendingPerson: OrderSourcePerson?
|
||||
|
||||
private let headerStack = UIStackView()
|
||||
private let titleLabel = UILabel()
|
||||
private let subtitleLabel = UILabel()
|
||||
private let leadHeaderRow = UIStackView()
|
||||
private let backButton = UIButton(type: .system)
|
||||
private let leadTitleLabel = UILabel()
|
||||
private let leadPhoneButton = UIButton(type: .system)
|
||||
private let leadCallButton = UIButton(type: .system)
|
||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||
private let loadingIndicator = UIActivityIndicatorView(style: .medium)
|
||||
private let emptyLabel = UILabel()
|
||||
|
||||
private var referralOrder: BoundReferralOrderEntity?
|
||||
|
||||
init(
|
||||
orderNumber: String,
|
||||
viewModel: OrderListViewModel,
|
||||
referralOrder: BoundReferralOrderEntity?,
|
||||
onPersonSelected: @escaping (OrderSourcePerson) -> Void,
|
||||
onLeadBound: @escaping (OrderSourceSelection) -> Void,
|
||||
onCallPhone: @escaping (String) -> Void,
|
||||
onPreviewImages: @escaping ([String], Int) -> Void
|
||||
) {
|
||||
self.orderNumber = orderNumber
|
||||
self.viewModel = viewModel
|
||||
self.onPersonSelected = onPersonSelected
|
||||
self.onLeadBound = onLeadBound
|
||||
self.onCallPhone = onCallPhone
|
||||
self.onPreviewImages = onPreviewImages
|
||||
self.referralOrder = referralOrder
|
||||
self.pendingPerson = viewModel.orderSourceSelection(
|
||||
orderNumber: orderNumber,
|
||||
referralOrder: referralOrder
|
||||
)?.person
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@ -33,44 +68,210 @@ final class OrderSourcePickerViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
title = showingLeads ? "选择带客单" : "选择获客员"
|
||||
view.backgroundColor = UIColor(hex: 0xF5F5F5)
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "关闭", style: .plain, target: self, action: #selector(closeTapped))
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
view.addSubview(tableView)
|
||||
tableView.snp.makeConstraints { $0.edges.equalToSuperview() }
|
||||
view.backgroundColor = .white
|
||||
setupUI()
|
||||
applyViewModel()
|
||||
}
|
||||
|
||||
@objc private func closeTapped() {
|
||||
dismiss(animated: true)
|
||||
func reloadFromViewModel() {
|
||||
applyViewModel()
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
titleLabel.font = .systemFont(ofSize: 18, weight: .bold)
|
||||
titleLabel.textColor = AppColor.text333
|
||||
titleLabel.text = "选择获客员"
|
||||
|
||||
subtitleLabel.font = .systemFont(ofSize: 13)
|
||||
subtitleLabel.textColor = AppColor.text666
|
||||
subtitleLabel.text = "选择人员后,再选择该人员创建的带客单"
|
||||
subtitleLabel.numberOfLines = 0
|
||||
|
||||
backButton.setImage(UIImage(systemName: "chevron.left"), for: .normal)
|
||||
backButton.tintColor = AppColor.text333
|
||||
backButton.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
|
||||
|
||||
leadTitleLabel.font = .systemFont(ofSize: 18, weight: .bold)
|
||||
leadTitleLabel.textColor = AppColor.text333
|
||||
leadTitleLabel.numberOfLines = 2
|
||||
|
||||
leadPhoneButton.titleLabel?.font = .systemFont(ofSize: 13)
|
||||
leadPhoneButton.setTitleColor(AppColor.primary, for: .normal)
|
||||
leadPhoneButton.contentHorizontalAlignment = .leading
|
||||
leadPhoneButton.addTarget(self, action: #selector(leadPhoneTapped), for: .touchUpInside)
|
||||
|
||||
leadCallButton.backgroundColor = OrderTokens.phoneIconBackground
|
||||
leadCallButton.layer.cornerRadius = 16
|
||||
leadCallButton.tintColor = AppColor.primary
|
||||
leadCallButton.setImage(UIImage(systemName: "phone.fill"), for: .normal)
|
||||
leadCallButton.addTarget(self, action: #selector(leadPhoneTapped), for: .touchUpInside)
|
||||
|
||||
leadHeaderRow.axis = .horizontal
|
||||
leadHeaderRow.alignment = .top
|
||||
leadHeaderRow.spacing = 10
|
||||
leadHeaderRow.isHidden = true
|
||||
let leadTextStack = UIStackView(arrangedSubviews: [leadTitleLabel, leadPhoneButton])
|
||||
leadTextStack.axis = .vertical
|
||||
leadTextStack.spacing = 3
|
||||
leadHeaderRow.addArrangedSubview(backButton)
|
||||
leadHeaderRow.addArrangedSubview(leadTextStack)
|
||||
leadHeaderRow.addArrangedSubview(leadCallButton)
|
||||
|
||||
headerStack.axis = .vertical
|
||||
headerStack.spacing = 5
|
||||
headerStack.addArrangedSubview(titleLabel)
|
||||
headerStack.addArrangedSubview(subtitleLabel)
|
||||
headerStack.addArrangedSubview(leadHeaderRow)
|
||||
|
||||
tableView.separatorStyle = .none
|
||||
tableView.backgroundColor = .white
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
tableView.register(OrderSourcePersonCell.self, forCellReuseIdentifier: OrderSourcePersonCell.reuseIdentifier)
|
||||
tableView.register(OrderSourceLeadCell.self, forCellReuseIdentifier: OrderSourceLeadCell.reuseIdentifier)
|
||||
|
||||
loadingIndicator.hidesWhenStopped = true
|
||||
emptyLabel.font = .systemFont(ofSize: 14)
|
||||
emptyLabel.textColor = AppColor.text666
|
||||
emptyLabel.textAlignment = .center
|
||||
emptyLabel.numberOfLines = 0
|
||||
|
||||
view.addSubview(headerStack)
|
||||
view.addSubview(tableView)
|
||||
view.addSubview(loadingIndicator)
|
||||
view.addSubview(emptyLabel)
|
||||
|
||||
headerStack.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.md)
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
}
|
||||
backButton.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(28)
|
||||
}
|
||||
leadCallButton.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(32)
|
||||
}
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.equalTo(headerStack.snp.bottom).offset(14)
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
}
|
||||
loadingIndicator.snp.makeConstraints { make in
|
||||
make.center.equalTo(tableView)
|
||||
}
|
||||
emptyLabel.snp.makeConstraints { make in
|
||||
make.center.equalTo(tableView)
|
||||
make.leading.trailing.equalTo(tableView).inset(AppSpacing.lg)
|
||||
}
|
||||
}
|
||||
|
||||
private func applyViewModel() {
|
||||
let currentSelection = viewModel.orderSourceSelection(
|
||||
orderNumber: orderNumber,
|
||||
referralOrder: referralOrder
|
||||
)
|
||||
titleLabel.isHidden = level == .lead
|
||||
subtitleLabel.isHidden = level == .lead
|
||||
leadHeaderRow.isHidden = level == .person
|
||||
|
||||
if level == .lead, let person = pendingPerson {
|
||||
leadTitleLabel.text = "\(person.name)创建的带客单"
|
||||
let phone = person.phone.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
leadPhoneButton.setTitle(phone, for: .normal)
|
||||
leadPhoneButton.isHidden = phone.isEmpty
|
||||
leadCallButton.isHidden = phone.isEmpty
|
||||
}
|
||||
|
||||
let isLoadingPeople = viewModel.isLoadingOrderSourcePeople && level == .person
|
||||
let isLoadingLeads = viewModel.loadingOrderSourceLeadsFor != nil && level == .lead
|
||||
if isLoadingPeople || isLoadingLeads {
|
||||
loadingIndicator.startAnimating()
|
||||
tableView.isHidden = true
|
||||
emptyLabel.isHidden = true
|
||||
} else {
|
||||
loadingIndicator.stopAnimating()
|
||||
tableView.isHidden = false
|
||||
let isEmpty: Bool
|
||||
switch level {
|
||||
case .person:
|
||||
isEmpty = viewModel.orderSourcePeople.isEmpty
|
||||
case .lead:
|
||||
isEmpty = leadsForPendingPerson.isEmpty
|
||||
}
|
||||
emptyLabel.isHidden = !isEmpty
|
||||
emptyLabel.text = level == .person ? "暂无合作获客员" : "暂无可绑定带客单"
|
||||
tableView.reloadData()
|
||||
}
|
||||
_ = currentSelection
|
||||
}
|
||||
|
||||
private var leadsForPendingPerson: [OrderSourceLead] {
|
||||
guard let person = pendingPerson else { return [] }
|
||||
return viewModel.orderSourceLeads[person.key] ?? []
|
||||
}
|
||||
|
||||
@objc private func backTapped() {
|
||||
level = .person
|
||||
applyViewModel()
|
||||
}
|
||||
|
||||
@objc private func leadPhoneTapped() {
|
||||
guard let phone = pendingPerson?.phone.trimmingCharacters(in: .whitespacesAndNewlines), !phone.isEmpty else { return }
|
||||
onCallPhone(phone)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
showingLeads ? leads.count : people.count
|
||||
switch level {
|
||||
case .person:
|
||||
return viewModel.orderSourcePeople.count
|
||||
case .lead:
|
||||
return leadsForPendingPerson.count
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
|
||||
if showingLeads {
|
||||
let lead = leads[indexPath.row]
|
||||
cell.textLabel?.text = lead.displayText
|
||||
cell.detailTextLabel?.text = lead.userMobile
|
||||
} else {
|
||||
let person = people[indexPath.row]
|
||||
cell.textLabel?.text = person.name
|
||||
cell.detailTextLabel?.text = person.phone
|
||||
let currentSelection = viewModel.orderSourceSelection(
|
||||
orderNumber: orderNumber,
|
||||
referralOrder: referralOrder
|
||||
)
|
||||
switch level {
|
||||
case .person:
|
||||
let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: OrderSourcePersonCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! OrderSourcePersonCell
|
||||
let person = viewModel.orderSourcePeople[indexPath.row]
|
||||
cell.configure(
|
||||
person: person,
|
||||
selected: currentSelection?.person.key == person.key
|
||||
)
|
||||
cell.onCallPhone = onCallPhone
|
||||
return cell
|
||||
case .lead:
|
||||
let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: OrderSourceLeadCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! OrderSourceLeadCell
|
||||
let lead = leadsForPendingPerson[indexPath.row]
|
||||
let selected = currentSelection?.person.key == pendingPerson?.key
|
||||
&& currentSelection?.lead.key == lead.key
|
||||
cell.configure(lead: lead, selected: selected)
|
||||
cell.onCallPhone = onCallPhone
|
||||
cell.onImageTap = onPreviewImages
|
||||
cell.onSelect = { [weak self] in
|
||||
guard let self, let person = self.pendingPerson else { return }
|
||||
let selection = OrderSourceSelection(person: person, lead: lead)
|
||||
self.onLeadBound(selection)
|
||||
}
|
||||
return cell
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if showingLeads, let person = selectedPerson {
|
||||
let lead = leads[indexPath.row]
|
||||
onLeadSelected?(OrderSourceSelection(person: person, lead: lead))
|
||||
dismiss(animated: true)
|
||||
} else {
|
||||
onPersonSelected?(people[indexPath.row])
|
||||
}
|
||||
guard level == .person else { return }
|
||||
let person = viewModel.orderSourcePeople[indexPath.row]
|
||||
pendingPerson = person
|
||||
level = .lead
|
||||
onPersonSelected(person)
|
||||
applyViewModel()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user