278 lines
10 KiB
Swift
278 lines
10 KiB
Swift
//
|
||
// OrderSourcePickerViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 订单来源选择 Bottom Sheet,对齐 Android `OrderSourcePicker` 弹层。
|
||
final class OrderSourcePickerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||
|
||
private enum MenuLevel {
|
||
case person
|
||
case lead
|
||
}
|
||
|
||
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
|
||
|
||
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)
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
view.backgroundColor = .white
|
||
setupUI()
|
||
applyViewModel()
|
||
}
|
||
|
||
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 {
|
||
switch level {
|
||
case .person:
|
||
return viewModel.orderSourcePeople.count
|
||
case .lead:
|
||
return leadsForPendingPerson.count
|
||
}
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||
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
|
||
}
|
||
}
|
||
|
||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||
guard level == .person else { return }
|
||
let person = viewModel.orderSourcePeople[indexPath.row]
|
||
pendingPerson = person
|
||
level = .lead
|
||
onPersonSelected(person)
|
||
applyViewModel()
|
||
}
|
||
}
|