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:
196
suixinkan/UI/CooperationOrder/BindAcquirerViewController.swift
Normal file
196
suixinkan/UI/CooperationOrder/BindAcquirerViewController.swift
Normal file
@ -0,0 +1,196 @@
|
||||
//
|
||||
// BindAcquirerViewController.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 接受邀请 / 绑定获客员页,对齐 Android `BindAcquirerScreen`。
|
||||
final class BindAcquirerViewController: BaseViewController {
|
||||
|
||||
var onBindSuccess: (() -> Void)?
|
||||
|
||||
private let saleUserId: Int
|
||||
private let viewModel = BindAcquirerViewModel()
|
||||
private let orderAPI = NetworkServices.shared.orderAPI
|
||||
|
||||
private let scrollView = UIScrollView()
|
||||
private let contentStack = UIStackView()
|
||||
private let headerCard = UIView()
|
||||
private let headerIcon = UIImageView()
|
||||
private let headerTitleLabel = UILabel()
|
||||
private let infoCard = UIView()
|
||||
private let salerRow = CooperationInfoRowView(label: "获客员")
|
||||
private let phoneRow = CooperationInfoRowView(label: "手机号")
|
||||
private let commissionField = UITextField()
|
||||
private let acceptButton = AppButton(title: "接受邀请")
|
||||
private let loadingIndicator = UIActivityIndicatorView(style: .large)
|
||||
private let bottomBar = UIView()
|
||||
|
||||
init(saleUserId: Int) {
|
||||
self.saleUserId = saleUserId
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "接受邀请"
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
contentStack.axis = .vertical
|
||||
contentStack.spacing = AppSpacing.sm
|
||||
|
||||
headerCard.backgroundColor = AppColor.primary
|
||||
headerCard.layer.cornerRadius = AppRadius.md
|
||||
headerIcon.image = UIImage(systemName: "person.fill")
|
||||
headerIcon.tintColor = .white
|
||||
headerTitleLabel.text = "邀请信息"
|
||||
headerTitleLabel.font = .systemFont(ofSize: 16, weight: .bold)
|
||||
headerTitleLabel.textColor = .white
|
||||
|
||||
infoCard.backgroundColor = .white
|
||||
infoCard.layer.cornerRadius = AppRadius.md
|
||||
|
||||
commissionField.placeholder = "请输入分账比例(0-100)"
|
||||
commissionField.keyboardType = .numberPad
|
||||
commissionField.borderStyle = .roundedRect
|
||||
commissionField.font = .systemFont(ofSize: 15)
|
||||
|
||||
bottomBar.backgroundColor = .white
|
||||
acceptButton.addTarget(self, action: #selector(acceptTapped), for: .touchUpInside)
|
||||
|
||||
loadingIndicator.hidesWhenStopped = true
|
||||
loadingIndicator.color = AppColor.primary
|
||||
|
||||
view.addSubview(scrollView)
|
||||
view.addSubview(bottomBar)
|
||||
scrollView.addSubview(contentStack)
|
||||
bottomBar.addSubview(acceptButton)
|
||||
scrollView.addSubview(loadingIndicator)
|
||||
|
||||
contentStack.addArrangedSubview(headerCard)
|
||||
contentStack.addArrangedSubview(infoCard)
|
||||
|
||||
headerCard.addSubview(headerIcon)
|
||||
headerCard.addSubview(headerTitleLabel)
|
||||
|
||||
let infoStack = UIStackView(arrangedSubviews: [salerRow, phoneRow, commissionField])
|
||||
infoStack.axis = .vertical
|
||||
infoStack.spacing = AppSpacing.sm
|
||||
infoCard.addSubview(infoStack)
|
||||
infoStack.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(AppSpacing.md)
|
||||
}
|
||||
|
||||
headerIcon.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(AppSpacing.md)
|
||||
make.centerY.equalToSuperview()
|
||||
make.size.equalTo(24)
|
||||
}
|
||||
headerTitleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalTo(headerIcon.snp.trailing).offset(AppSpacing.sm)
|
||||
make.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.top.bottom.equalToSuperview().inset(AppSpacing.md)
|
||||
}
|
||||
headerCard.snp.makeConstraints { make in
|
||||
make.height.greaterThanOrEqualTo(56)
|
||||
}
|
||||
commissionField.snp.makeConstraints { make in
|
||||
make.height.equalTo(44)
|
||||
}
|
||||
}
|
||||
|
||||
override func setupConstraints() {
|
||||
bottomBar.snp.makeConstraints { make in
|
||||
make.leading.trailing.bottom.equalToSuperview()
|
||||
}
|
||||
acceptButton.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(AppSpacing.md)
|
||||
}
|
||||
scrollView.snp.makeConstraints { make in
|
||||
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
|
||||
make.bottom.equalTo(bottomBar.snp.top)
|
||||
}
|
||||
contentStack.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(AppSpacing.md)
|
||||
make.width.equalTo(scrollView.snp.width).offset(-AppSpacing.md * 2)
|
||||
}
|
||||
loadingIndicator.snp.makeConstraints { make in
|
||||
make.center.equalTo(scrollView)
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
viewModel.onBindSuccess = { [weak self] in
|
||||
Task { @MainActor in
|
||||
if let callback = self?.onBindSuccess {
|
||||
callback()
|
||||
} else {
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModel.onShouldPop = { [weak self] in
|
||||
Task { @MainActor in self?.navigationController?.popViewController(animated: true) }
|
||||
}
|
||||
commissionField.addTarget(self, action: #selector(commissionChanged), for: .editingChanged)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
guard CooperationOrderFeature.hasPermission(in: AppStore.shared.permissionItems()) else {
|
||||
showToast("暂无合作订单权限")
|
||||
navigationController?.popViewController(animated: false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
viewModel.initSaleUserId(saleUserId)
|
||||
Task { await viewModel.loadInviteInfo(api: orderAPI) }
|
||||
}
|
||||
|
||||
@objc private func commissionChanged() {
|
||||
viewModel.updateCommissionRateDraft(commissionField.text ?? "")
|
||||
}
|
||||
|
||||
@objc private func acceptTapped() {
|
||||
Task { await viewModel.acceptBind(api: orderAPI) }
|
||||
}
|
||||
|
||||
private func applyViewModel() {
|
||||
if viewModel.isLoading, viewModel.inviteInfo == nil {
|
||||
loadingIndicator.startAnimating()
|
||||
contentStack.isHidden = true
|
||||
bottomBar.isHidden = true
|
||||
return
|
||||
}
|
||||
loadingIndicator.stopAnimating()
|
||||
contentStack.isHidden = false
|
||||
bottomBar.isHidden = false
|
||||
|
||||
if let info = viewModel.inviteInfo {
|
||||
title = info.title.isEmpty ? "接受邀请" : info.title
|
||||
salerRow.setValue(info.salerName.isEmpty ? "—" : info.salerName)
|
||||
phoneRow.setValue(info.salerPhone.isEmpty ? "—" : info.salerPhone)
|
||||
}
|
||||
|
||||
commissionField.text = viewModel.commissionRateDraft
|
||||
commissionField.isEnabled = !viewModel.isAlreadyBound
|
||||
acceptButton.isEnabled = !viewModel.isAlreadyBound
|
||||
acceptButton.setTitle(viewModel.isAlreadyBound ? "已绑定获客员" : "接受邀请", for: .normal)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,231 @@
|
||||
//
|
||||
// CooperationAcquirerViewController.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 合作获客员页,对齐 Android `CooperationAcquirerScreen`。
|
||||
final class CooperationAcquirerViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
private let viewModel = CooperationAcquirerViewModel()
|
||||
private let orderAPI = NetworkServices.shared.orderAPI
|
||||
|
||||
private let tableView = UITableView(frame: .zero, style: .plain)
|
||||
private let refreshControl = UIRefreshControl()
|
||||
private let emptyLabel = UILabel()
|
||||
private var shouldRefreshOnAppear = false
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "合作获客员"
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: "扫码绑定",
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(startScanBind)
|
||||
)
|
||||
navigationItem.rightBarButtonItem?.tintColor = AppColor.primary
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.separatorStyle = .none
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
tableView.register(CooperationAcquirerCell.self, forCellReuseIdentifier: CooperationAcquirerCell.reuseIdentifier)
|
||||
tableView.refreshControl = refreshControl
|
||||
|
||||
emptyLabel.text = "暂无合作获客员"
|
||||
emptyLabel.font = .systemFont(ofSize: 14)
|
||||
emptyLabel.textColor = AppColor.textTertiary
|
||||
emptyLabel.textAlignment = .center
|
||||
emptyLabel.isHidden = true
|
||||
|
||||
view.addSubview(tableView)
|
||||
view.addSubview(emptyLabel)
|
||||
}
|
||||
|
||||
override func setupConstraints() {
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.edges.equalTo(view.safeAreaLayoutGuide)
|
||||
}
|
||||
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) }
|
||||
}
|
||||
viewModel.onNavigateBindAcquirer = { [weak self] saleUserId in
|
||||
Task { @MainActor in self?.pushBindAcquirer(saleUserId: saleUserId) }
|
||||
}
|
||||
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
|
||||
}
|
||||
if shouldRefreshOnAppear {
|
||||
shouldRefreshOnAppear = false
|
||||
Task { await viewModel.refresh(api: orderAPI) }
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
Task { await viewModel.loadAcquirers(api: orderAPI, initial: true) }
|
||||
}
|
||||
|
||||
@objc private func startScanBind() {
|
||||
switch AVCaptureDevice.authorizationStatus(for: .video) {
|
||||
case .authorized:
|
||||
presentScanner()
|
||||
case .notDetermined:
|
||||
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
|
||||
Task { @MainActor in
|
||||
if granted {
|
||||
self?.presentScanner()
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
showToast("需要相机权限才能扫码")
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func refreshTriggered() {
|
||||
Task {
|
||||
await viewModel.refresh(api: orderAPI)
|
||||
refreshControl.endRefreshing()
|
||||
}
|
||||
}
|
||||
|
||||
private func presentScanner() {
|
||||
let scanner = QRCodeScannerViewController()
|
||||
scanner.onScanResult = { [weak self] result in
|
||||
self?.viewModel.processScanResult(result)
|
||||
}
|
||||
let nav = UINavigationController(rootViewController: scanner)
|
||||
nav.modalPresentationStyle = .fullScreen
|
||||
present(nav, animated: true)
|
||||
}
|
||||
|
||||
private func pushBindAcquirer(saleUserId: Int) {
|
||||
let controller = BindAcquirerViewController(saleUserId: saleUserId)
|
||||
controller.onBindSuccess = { [weak self] in
|
||||
self?.shouldRefreshOnAppear = true
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
if presentedViewController != nil {
|
||||
dismiss(animated: true) { [weak self] in
|
||||
self?.navigationController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
return
|
||||
}
|
||||
navigationController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
|
||||
private func applyViewModel() {
|
||||
emptyLabel.isHidden = !viewModel.acquirers.isEmpty
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func presentRemarkDialog(for acquirer: CooperativeSalerEntity) {
|
||||
viewModel.startEditRemark(acquirer)
|
||||
let alert = UIAlertController(title: "修改备注", message: "最多\(CooperationOrderFeature.remarkNameMaxLength)个字符", preferredStyle: .alert)
|
||||
alert.addTextField { [weak self] field in
|
||||
field.text = self?.viewModel.remarkDraft
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel) { [weak self] _ in
|
||||
self?.viewModel.dismissRemarkDialog()
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "保存", style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
if let text = alert.textFields?.first?.text {
|
||||
self.viewModel.updateRemarkDraft(text)
|
||||
}
|
||||
Task { await self.viewModel.saveRemark(api: self.orderAPI) }
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func presentCommissionDialog(for acquirer: CooperativeSalerEntity) {
|
||||
viewModel.startEditCommissionRate(acquirer)
|
||||
let alert = UIAlertController(title: "修改分成比例", message: nil, preferredStyle: .alert)
|
||||
alert.addTextField { [weak self] field in
|
||||
field.placeholder = "分成比例 0-100"
|
||||
field.keyboardType = .numberPad
|
||||
field.text = self?.viewModel.commissionRateDraft
|
||||
}
|
||||
alert.addTextField { [weak self] field in
|
||||
field.placeholder = "验证码"
|
||||
field.keyboardType = .numberPad
|
||||
field.text = self?.viewModel.commissionSmsCodeDraft
|
||||
}
|
||||
let phone = acquirer.salerPhone.isEmpty ? acquirer.displayPhone : acquirer.salerPhone
|
||||
alert.message = "验证码将发送至 \(CooperationOrderPhoneMask.mask(phone))"
|
||||
|
||||
alert.addAction(UIAlertAction(title: "取消", style: .cancel) { [weak self] _ in
|
||||
self?.viewModel.dismissCommissionRateDialog()
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "获取验证码", style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
if let rateText = alert.textFields?.first?.text {
|
||||
self.viewModel.updateCommissionRateDraft(rateText)
|
||||
}
|
||||
if let codeText = alert.textFields?.last?.text {
|
||||
self.viewModel.updateCommissionSmsCodeDraft(codeText)
|
||||
}
|
||||
Task { await self.viewModel.requestCommissionSmsCode(api: self.orderAPI) }
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "确认修改", style: .default) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
if let rateText = alert.textFields?.first?.text {
|
||||
self.viewModel.updateCommissionRateDraft(rateText)
|
||||
}
|
||||
if let codeText = alert.textFields?.last?.text {
|
||||
self.viewModel.updateCommissionSmsCodeDraft(codeText)
|
||||
}
|
||||
Task { await self.viewModel.saveCommissionRate(api: self.orderAPI) }
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
viewModel.acquirers.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let cell = tableView.dequeueReusableCell(
|
||||
withIdentifier: CooperationAcquirerCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as? CooperationAcquirerCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
let acquirer = viewModel.acquirers[indexPath.row]
|
||||
cell.configure(with: acquirer)
|
||||
cell.onEditRemark = { [weak self] in
|
||||
self?.presentRemarkDialog(for: acquirer)
|
||||
}
|
||||
cell.onEditCommission = { [weak self] in
|
||||
self?.presentCommissionDialog(for: acquirer)
|
||||
}
|
||||
cell.onCall = {
|
||||
let phone = acquirer.displayPhone.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !phone.isEmpty, let url = URL(string: "tel://\(phone)") else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
return cell
|
||||
}
|
||||
}
|
||||
@ -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) }
|
||||
}
|
||||
}
|
||||
102
suixinkan/UI/CooperationOrder/ImagePreviewViewController.swift
Normal file
102
suixinkan/UI/CooperationOrder/ImagePreviewViewController.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// ImagePreviewViewController.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 图片全屏预览,对齐 Android `MediaPreviewDialog`。
|
||||
final class ImagePreviewViewController: UIViewController {
|
||||
|
||||
private let imageURLs: [String]
|
||||
private let startIndex: Int
|
||||
private let scrollView = UIScrollView()
|
||||
private let pageControl = UIPageControl()
|
||||
|
||||
init(imageURLs: [String], startIndex: Int) {
|
||||
self.imageURLs = imageURLs
|
||||
self.startIndex = min(max(0, startIndex), max(0, imageURLs.count - 1))
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
modalPresentationStyle = .fullScreen
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .black
|
||||
|
||||
scrollView.isPagingEnabled = true
|
||||
scrollView.showsHorizontalScrollIndicator = false
|
||||
scrollView.delegate = self
|
||||
|
||||
pageControl.numberOfPages = imageURLs.count
|
||||
pageControl.currentPage = startIndex
|
||||
pageControl.isHidden = imageURLs.count <= 1
|
||||
|
||||
let closeButton = UIButton(type: .system)
|
||||
closeButton.setImage(UIImage(systemName: "xmark"), for: .normal)
|
||||
closeButton.tintColor = .white
|
||||
closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
|
||||
|
||||
view.addSubview(scrollView)
|
||||
view.addSubview(pageControl)
|
||||
view.addSubview(closeButton)
|
||||
|
||||
scrollView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
pageControl.snp.makeConstraints { make in
|
||||
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(AppSpacing.md)
|
||||
make.centerX.equalToSuperview()
|
||||
}
|
||||
closeButton.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.sm)
|
||||
make.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.size.equalTo(AppSpacing.minTouchTarget)
|
||||
}
|
||||
|
||||
imageURLs.enumerated().forEach { index, url in
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.loadRemoteImage(urlString: url, placeholderColor: .darkGray)
|
||||
scrollView.addSubview(imageView)
|
||||
imageView.snp.makeConstraints { make in
|
||||
make.top.bottom.equalToSuperview()
|
||||
make.width.equalTo(view.snp.width)
|
||||
make.height.equalTo(view.snp.height)
|
||||
make.leading.equalToSuperview().offset(CGFloat(index) * view.bounds.width)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
scrollView.contentSize = CGSize(width: view.bounds.width * CGFloat(imageURLs.count), height: view.bounds.height)
|
||||
scrollView.setContentOffset(CGPoint(x: view.bounds.width * CGFloat(startIndex), y: 0), animated: false)
|
||||
scrollView.subviews.enumerated().forEach { index, subview in
|
||||
subview.frame = CGRect(
|
||||
x: view.bounds.width * CGFloat(index),
|
||||
y: 0,
|
||||
width: view.bounds.width,
|
||||
height: view.bounds.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func closeTapped() {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImagePreviewViewController: UIScrollViewDelegate {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard view.bounds.width > 0 else { return }
|
||||
let page = Int(round(scrollView.contentOffset.x / view.bounds.width))
|
||||
pageControl.currentPage = page
|
||||
}
|
||||
}
|
||||
595
suixinkan/UI/CooperationOrder/Views/CooperationOrderViews.swift
Normal file
595
suixinkan/UI/CooperationOrder/Views/CooperationOrderViews.swift
Normal file
@ -0,0 +1,595 @@
|
||||
//
|
||||
// CooperationOrderViews.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Kingfisher
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 合作订单 Tab 切换栏。
|
||||
final class CooperationOrderTabBar: UIView {
|
||||
|
||||
var onTabSelected: ((Int) -> Void)?
|
||||
|
||||
private let stack = UIStackView()
|
||||
private var buttons: [UIButton] = []
|
||||
private var selectedIndex = 0
|
||||
private let indicator = UIView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
stack.axis = .horizontal
|
||||
stack.distribution = .fillEqually
|
||||
addSubview(stack)
|
||||
addSubview(indicator)
|
||||
indicator.backgroundColor = AppColor.primary
|
||||
indicator.layer.cornerRadius = 1.5
|
||||
|
||||
["获客员线索", "获客订单"].enumerated().forEach { index, title in
|
||||
let button = UIButton(type: .system)
|
||||
button.setTitle(title, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 15, weight: index == 0 ? .bold : .regular)
|
||||
button.setTitleColor(index == 0 ? AppColor.primary : AppColor.textSecondary, for: .normal)
|
||||
button.tag = index
|
||||
button.addTarget(self, action: #selector(tabTapped(_:)), for: .touchUpInside)
|
||||
buttons.append(button)
|
||||
stack.addArrangedSubview(button)
|
||||
}
|
||||
|
||||
stack.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
make.height.equalTo(48)
|
||||
}
|
||||
layoutIndicator(animated: false)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setSelectedTab(_ index: Int) {
|
||||
guard selectedIndex != index else { return }
|
||||
selectedIndex = index
|
||||
updateButtonStyles()
|
||||
layoutIndicator(animated: true)
|
||||
}
|
||||
|
||||
@objc private func tabTapped(_ sender: UIButton) {
|
||||
setSelectedTab(sender.tag)
|
||||
onTabSelected?(sender.tag)
|
||||
}
|
||||
|
||||
private func updateButtonStyles() {
|
||||
buttons.enumerated().forEach { index, button in
|
||||
let selected = index == selectedIndex
|
||||
button.setTitleColor(selected ? AppColor.primary : AppColor.textSecondary, for: .normal)
|
||||
button.titleLabel?.font = .systemFont(ofSize: 15, weight: selected ? .bold : .regular)
|
||||
}
|
||||
}
|
||||
|
||||
private func layoutIndicator(animated: Bool) {
|
||||
guard !buttons.isEmpty else { return }
|
||||
let button = buttons[selectedIndex]
|
||||
let apply = {
|
||||
self.indicator.snp.remakeConstraints { make in
|
||||
make.bottom.equalToSuperview()
|
||||
make.height.equalTo(3)
|
||||
make.width.equalTo(button.snp.width).multipliedBy(0.5)
|
||||
make.centerX.equalTo(button)
|
||||
}
|
||||
self.layoutIfNeeded()
|
||||
}
|
||||
if animated {
|
||||
UIView.animate(withDuration: 0.2, animations: apply)
|
||||
} else {
|
||||
apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获客订单搜索栏。
|
||||
final class CooperationOrderSearchBar: UIView, UITextFieldDelegate {
|
||||
|
||||
var onSearchTextChange: ((String) -> Void)?
|
||||
var onSearch: (() -> Void)?
|
||||
|
||||
let textField = UITextField()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .white
|
||||
|
||||
let container = UIView()
|
||||
container.backgroundColor = AppColor.inputBackground
|
||||
container.layer.cornerRadius = AppRadius.sm
|
||||
|
||||
let icon = UIImageView(image: UIImage(systemName: "magnifyingglass"))
|
||||
icon.tintColor = AppColor.textTertiary
|
||||
|
||||
textField.placeholder = "名称搜索"
|
||||
textField.font = .systemFont(ofSize: 14)
|
||||
textField.textColor = AppColor.textPrimary
|
||||
textField.returnKeyType = .search
|
||||
textField.delegate = self
|
||||
textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
|
||||
|
||||
addSubview(container)
|
||||
container.addSubview(icon)
|
||||
container.addSubview(textField)
|
||||
|
||||
snp.makeConstraints { make in
|
||||
make.height.equalTo(62)
|
||||
}
|
||||
container.snp.makeConstraints { make in
|
||||
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
||||
make.centerY.equalToSuperview()
|
||||
make.height.equalTo(38)
|
||||
}
|
||||
icon.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(AppSpacing.sm)
|
||||
make.centerY.equalToSuperview()
|
||||
make.size.equalTo(18)
|
||||
}
|
||||
textField.snp.makeConstraints { make in
|
||||
make.leading.equalTo(icon.snp.trailing).offset(AppSpacing.xs)
|
||||
make.trailing.equalToSuperview().inset(AppSpacing.sm)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func textChanged() {
|
||||
onSearchTextChange?(textField.text ?? "")
|
||||
}
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField.resignFirstResponder()
|
||||
onSearch?()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// 获客员线索 Cell。
|
||||
final class ReferralLeadCell: UITableViewCell {
|
||||
static let reuseIdentifier = "ReferralLeadCell"
|
||||
|
||||
var onImageTap: ((Int, [String]) -> Void)?
|
||||
|
||||
private let cardView = UIView()
|
||||
private let phoneLabel = UILabel()
|
||||
private let phoneValueLabel = UILabel()
|
||||
private let timeLabel = UILabel()
|
||||
private let salerRow = CooperationInfoRowView(label: "获客员")
|
||||
private let remarkTitleLabel = UILabel()
|
||||
private let remarkLabel = UILabel()
|
||||
private let imageTitleLabel = UILabel()
|
||||
private let imageStack = UIStackView()
|
||||
private let emptyImageLabel = UILabel()
|
||||
private var imageURLs: [String] = []
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
contentView.backgroundColor = .clear
|
||||
|
||||
cardView.backgroundColor = .white
|
||||
cardView.layer.cornerRadius = AppRadius.lg
|
||||
cardView.layer.shadowColor = UIColor.black.cgColor
|
||||
cardView.layer.shadowOpacity = 0.04
|
||||
cardView.layer.shadowOffset = CGSize(width: 0, height: 1)
|
||||
cardView.layer.shadowRadius = 2
|
||||
|
||||
phoneLabel.text = "用户手机号"
|
||||
phoneLabel.font = .systemFont(ofSize: 13)
|
||||
phoneLabel.textColor = AppColor.textTertiary
|
||||
phoneValueLabel.font = .systemFont(ofSize: 15, weight: .medium)
|
||||
phoneValueLabel.textColor = AppColor.textPrimary
|
||||
timeLabel.font = .systemFont(ofSize: 11)
|
||||
timeLabel.textColor = AppColor.textTertiary
|
||||
timeLabel.textAlignment = .right
|
||||
|
||||
remarkTitleLabel.text = "备注"
|
||||
remarkTitleLabel.font = .systemFont(ofSize: 13)
|
||||
remarkTitleLabel.textColor = AppColor.textTertiary
|
||||
remarkLabel.font = .systemFont(ofSize: 14)
|
||||
remarkLabel.textColor = AppColor.textPrimary
|
||||
remarkLabel.numberOfLines = 0
|
||||
|
||||
imageTitleLabel.text = "图片"
|
||||
imageTitleLabel.font = .systemFont(ofSize: 13)
|
||||
imageTitleLabel.textColor = AppColor.textTertiary
|
||||
|
||||
imageStack.axis = .horizontal
|
||||
imageStack.spacing = AppSpacing.xs
|
||||
imageStack.distribution = .fillEqually
|
||||
|
||||
emptyImageLabel.text = "暂无图片"
|
||||
emptyImageLabel.font = .systemFont(ofSize: 13)
|
||||
emptyImageLabel.textColor = AppColor.textTertiary
|
||||
emptyImageLabel.textAlignment = .center
|
||||
emptyImageLabel.backgroundColor = UIColor(hex: 0xF7F8FA)
|
||||
emptyImageLabel.layer.cornerRadius = AppRadius.sm
|
||||
emptyImageLabel.clipsToBounds = true
|
||||
emptyImageLabel.isHidden = true
|
||||
|
||||
contentView.addSubview(cardView)
|
||||
cardView.addSubview(phoneLabel)
|
||||
cardView.addSubview(phoneValueLabel)
|
||||
cardView.addSubview(timeLabel)
|
||||
cardView.addSubview(salerRow)
|
||||
cardView.addSubview(remarkTitleLabel)
|
||||
cardView.addSubview(remarkLabel)
|
||||
cardView.addSubview(imageTitleLabel)
|
||||
cardView.addSubview(imageStack)
|
||||
cardView.addSubview(emptyImageLabel)
|
||||
|
||||
cardView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md))
|
||||
}
|
||||
phoneLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(14)
|
||||
}
|
||||
phoneValueLabel.snp.makeConstraints { make in
|
||||
make.leading.equalTo(phoneLabel.snp.trailing).offset(14)
|
||||
make.centerY.equalTo(phoneLabel)
|
||||
}
|
||||
timeLabel.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().inset(14)
|
||||
make.centerY.equalTo(phoneLabel)
|
||||
make.leading.greaterThanOrEqualTo(phoneValueLabel.snp.trailing).offset(AppSpacing.xs)
|
||||
}
|
||||
salerRow.snp.makeConstraints { make in
|
||||
make.top.equalTo(phoneLabel.snp.bottom).offset(10)
|
||||
make.leading.trailing.equalToSuperview().inset(14)
|
||||
}
|
||||
remarkTitleLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(salerRow.snp.bottom).offset(10)
|
||||
make.leading.equalToSuperview().offset(14)
|
||||
}
|
||||
remarkLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(remarkTitleLabel.snp.bottom).offset(4)
|
||||
make.leading.trailing.equalToSuperview().inset(14)
|
||||
}
|
||||
imageTitleLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(remarkLabel.snp.bottom).offset(10)
|
||||
make.leading.equalToSuperview().offset(14)
|
||||
}
|
||||
imageStack.snp.makeConstraints { make in
|
||||
make.top.equalTo(imageTitleLabel.snp.bottom).offset(4)
|
||||
make.leading.trailing.equalToSuperview().inset(14)
|
||||
make.height.equalTo(imageStack.snp.width).multipliedBy(1.0 / 3.0).offset(-AppSpacing.xs * 2 / 3)
|
||||
make.bottom.equalToSuperview().inset(14)
|
||||
}
|
||||
emptyImageLabel.snp.makeConstraints { make in
|
||||
make.edges.equalTo(imageStack)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configure(with lead: ReferralLeadEntity) {
|
||||
phoneValueLabel.text = lead.displayPhone.isEmpty ? "—" : lead.displayPhone
|
||||
timeLabel.text = lead.createdAt
|
||||
salerRow.setValue(lead.displaySalerName.isEmpty ? "—" : lead.displaySalerName)
|
||||
remarkLabel.text = lead.remark.isEmpty ? "暂无备注" : lead.remark
|
||||
imageURLs = lead.displayImages
|
||||
imageStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
if imageURLs.isEmpty {
|
||||
emptyImageLabel.isHidden = false
|
||||
imageStack.isHidden = true
|
||||
} else {
|
||||
emptyImageLabel.isHidden = true
|
||||
imageStack.isHidden = false
|
||||
imageURLs.prefix(3).enumerated().forEach { index, url in
|
||||
let imageView = UIImageView()
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.clipsToBounds = true
|
||||
imageView.layer.cornerRadius = AppRadius.sm
|
||||
imageView.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||||
imageView.isUserInteractionEnabled = true
|
||||
imageView.loadRemoteImage(urlString: url)
|
||||
imageView.tag = index
|
||||
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:))))
|
||||
imageStack.addArrangedSubview(imageView)
|
||||
}
|
||||
let missing = 3 - imageStack.arrangedSubviews.count
|
||||
if missing > 0 {
|
||||
(0..<missing).forEach { _ in
|
||||
let spacer = UIView()
|
||||
imageStack.addArrangedSubview(spacer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func imageTapped(_ gesture: UITapGestureRecognizer) {
|
||||
guard let index = gesture.view?.tag else { return }
|
||||
onImageTap?(index, imageURLs)
|
||||
}
|
||||
}
|
||||
|
||||
/// 获客订单 Cell。
|
||||
final class AcquisitionOrderCell: UITableViewCell {
|
||||
static let reuseIdentifier = "AcquisitionOrderCell"
|
||||
|
||||
private let cardView = UIView()
|
||||
private let titleLabel = UILabel()
|
||||
private let typeTag = UILabel()
|
||||
private let orderNumberLabel = UILabel()
|
||||
private let partnerRow = CooperationInfoRowView(label: "合作人员")
|
||||
private let timeRow = CooperationInfoRowView(label: "下单时间")
|
||||
private let customerLabel = UILabel()
|
||||
private let amountLabel = UILabel()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
contentView.backgroundColor = .clear
|
||||
|
||||
cardView.backgroundColor = .white
|
||||
cardView.layer.cornerRadius = AppRadius.lg
|
||||
cardView.layer.shadowColor = UIColor.black.cgColor
|
||||
cardView.layer.shadowOpacity = 0.04
|
||||
cardView.layer.shadowOffset = CGSize(width: 0, height: 1)
|
||||
cardView.layer.shadowRadius = 2
|
||||
|
||||
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
|
||||
titleLabel.textColor = AppColor.textPrimary
|
||||
|
||||
typeTag.font = .systemFont(ofSize: 11)
|
||||
typeTag.textColor = AppColor.primary
|
||||
typeTag.backgroundColor = AppColor.primaryLight
|
||||
typeTag.layer.cornerRadius = AppRadius.xs
|
||||
typeTag.clipsToBounds = true
|
||||
typeTag.textAlignment = .center
|
||||
|
||||
orderNumberLabel.font = .systemFont(ofSize: 13)
|
||||
orderNumberLabel.textColor = AppColor.textSecondary
|
||||
|
||||
customerLabel.font = .systemFont(ofSize: 12)
|
||||
customerLabel.textColor = AppColor.textTertiary
|
||||
|
||||
amountLabel.font = .systemFont(ofSize: 17, weight: .bold)
|
||||
amountLabel.textColor = AppColor.primary
|
||||
amountLabel.textAlignment = .right
|
||||
|
||||
contentView.addSubview(cardView)
|
||||
cardView.addSubview(titleLabel)
|
||||
cardView.addSubview(typeTag)
|
||||
cardView.addSubview(orderNumberLabel)
|
||||
cardView.addSubview(partnerRow)
|
||||
cardView.addSubview(timeRow)
|
||||
cardView.addSubview(customerLabel)
|
||||
cardView.addSubview(amountLabel)
|
||||
|
||||
cardView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md))
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(14)
|
||||
make.trailing.lessThanOrEqualToSuperview().inset(14)
|
||||
}
|
||||
typeTag.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(10)
|
||||
make.leading.equalToSuperview().offset(14)
|
||||
make.height.equalTo(22)
|
||||
}
|
||||
orderNumberLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(typeTag)
|
||||
make.leading.equalTo(typeTag.snp.trailing).offset(AppSpacing.xs)
|
||||
make.trailing.lessThanOrEqualToSuperview().inset(14)
|
||||
}
|
||||
partnerRow.snp.makeConstraints { make in
|
||||
make.top.equalTo(typeTag.snp.bottom).offset(10)
|
||||
make.leading.trailing.equalToSuperview().inset(14)
|
||||
}
|
||||
timeRow.snp.makeConstraints { make in
|
||||
make.top.equalTo(partnerRow.snp.bottom).offset(10)
|
||||
make.leading.trailing.equalToSuperview().inset(14)
|
||||
}
|
||||
customerLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(timeRow.snp.bottom).offset(10)
|
||||
make.leading.equalToSuperview().offset(14)
|
||||
make.bottom.equalToSuperview().inset(14)
|
||||
}
|
||||
amountLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(customerLabel)
|
||||
make.trailing.equalToSuperview().inset(14)
|
||||
make.leading.greaterThanOrEqualTo(customerLabel.snp.trailing).offset(AppSpacing.xs)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configure(with order: AcquisitionOrderEntity) {
|
||||
titleLabel.text = order.projectName.isEmpty ? "—" : order.projectName
|
||||
if order.orderTypeLabel.isEmpty {
|
||||
typeTag.isHidden = true
|
||||
orderNumberLabel.snp.remakeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(10)
|
||||
make.leading.equalToSuperview().offset(14)
|
||||
make.trailing.lessThanOrEqualToSuperview().inset(14)
|
||||
}
|
||||
} else {
|
||||
typeTag.isHidden = false
|
||||
typeTag.text = " \(order.orderTypeLabel) "
|
||||
orderNumberLabel.snp.remakeConstraints { make in
|
||||
make.centerY.equalTo(typeTag)
|
||||
make.leading.equalTo(typeTag.snp.trailing).offset(AppSpacing.xs)
|
||||
make.trailing.lessThanOrEqualToSuperview().inset(14)
|
||||
}
|
||||
}
|
||||
orderNumberLabel.text = "订单号 \(order.orderNumber)"
|
||||
partnerRow.setValue(order.displayPartner.isEmpty ? "暂未绑定" : order.displayPartner)
|
||||
timeRow.setValue(order.createdAt.isEmpty ? "—" : order.createdAt)
|
||||
customerLabel.text = "客户 \(order.displayCustomerPhone.isEmpty ? "—" : order.displayCustomerPhone)"
|
||||
amountLabel.text = "¥\(order.displayAmount)"
|
||||
}
|
||||
}
|
||||
|
||||
/// 合作获客员 Cell。
|
||||
final class CooperationAcquirerCell: UITableViewCell {
|
||||
static let reuseIdentifier = "CooperationAcquirerCell"
|
||||
|
||||
var onEditRemark: (() -> Void)?
|
||||
var onEditCommission: (() -> Void)?
|
||||
var onCall: (() -> Void)?
|
||||
|
||||
private let cardView = UIView()
|
||||
private let nameLabel = UILabel()
|
||||
private let editRemarkButton = UIButton(type: .system)
|
||||
private let phoneLabel = UILabel()
|
||||
private let callButton = UIButton(type: .system)
|
||||
private let bindTimeRow = CooperationInfoRowView(label: "绑定时间")
|
||||
private let commissionRow = CooperationInfoRowView(label: "分成比例")
|
||||
private let editCommissionButton = UIButton(type: .system)
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
contentView.backgroundColor = .clear
|
||||
|
||||
cardView.backgroundColor = .white
|
||||
cardView.layer.cornerRadius = AppRadius.lg
|
||||
|
||||
nameLabel.font = .systemFont(ofSize: 16, weight: .bold)
|
||||
nameLabel.textColor = AppColor.textPrimary
|
||||
|
||||
editRemarkButton.setTitle("修改备注", for: .normal)
|
||||
editRemarkButton.titleLabel?.font = .systemFont(ofSize: 13)
|
||||
editRemarkButton.setTitleColor(AppColor.primary, for: .normal)
|
||||
editRemarkButton.addTarget(self, action: #selector(editRemarkTapped), for: .touchUpInside)
|
||||
|
||||
phoneLabel.font = .systemFont(ofSize: 13)
|
||||
phoneLabel.textColor = AppColor.textSecondary
|
||||
|
||||
callButton.setImage(UIImage(systemName: "phone.fill"), for: .normal)
|
||||
callButton.tintColor = AppColor.primary
|
||||
callButton.backgroundColor = AppColor.primaryLight
|
||||
callButton.layer.cornerRadius = 15
|
||||
callButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside)
|
||||
|
||||
editCommissionButton.setTitle("修改", for: .normal)
|
||||
editCommissionButton.titleLabel?.font = .systemFont(ofSize: 13)
|
||||
editCommissionButton.setTitleColor(AppColor.primary, for: .normal)
|
||||
editCommissionButton.addTarget(self, action: #selector(editCommissionTapped), for: .touchUpInside)
|
||||
|
||||
contentView.addSubview(cardView)
|
||||
cardView.addSubview(nameLabel)
|
||||
cardView.addSubview(editRemarkButton)
|
||||
cardView.addSubview(phoneLabel)
|
||||
cardView.addSubview(callButton)
|
||||
cardView.addSubview(bindTimeRow)
|
||||
cardView.addSubview(commissionRow)
|
||||
cardView.addSubview(editCommissionButton)
|
||||
|
||||
cardView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md))
|
||||
}
|
||||
nameLabel.snp.makeConstraints { make in
|
||||
make.top.leading.equalToSuperview().offset(14)
|
||||
}
|
||||
editRemarkButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(nameLabel)
|
||||
make.leading.equalTo(nameLabel.snp.trailing).offset(AppSpacing.xs)
|
||||
}
|
||||
phoneLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(nameLabel.snp.bottom).offset(10)
|
||||
make.leading.equalToSuperview().offset(14)
|
||||
}
|
||||
callButton.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().inset(14)
|
||||
make.centerY.equalTo(phoneLabel)
|
||||
make.size.equalTo(30)
|
||||
}
|
||||
bindTimeRow.snp.makeConstraints { make in
|
||||
make.top.equalTo(phoneLabel.snp.bottom).offset(10)
|
||||
make.leading.trailing.equalToSuperview().inset(14)
|
||||
}
|
||||
commissionRow.snp.makeConstraints { make in
|
||||
make.top.equalTo(bindTimeRow.snp.bottom).offset(10)
|
||||
make.leading.equalToSuperview().offset(14)
|
||||
make.bottom.equalToSuperview().inset(14)
|
||||
}
|
||||
editCommissionButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(commissionRow)
|
||||
make.leading.equalTo(commissionRow.snp.trailing).offset(AppSpacing.xs)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configure(with acquirer: CooperativeSalerEntity) {
|
||||
nameLabel.text = acquirer.displayName.isEmpty ? "—" : acquirer.displayName
|
||||
phoneLabel.text = "手机号 \(CooperationOrderPhoneMask.mask(acquirer.displayPhone))"
|
||||
bindTimeRow.setValue(acquirer.displayBindTime.isEmpty ? "—" : acquirer.displayBindTime)
|
||||
commissionRow.setValue(acquirer.displayCommissionRate)
|
||||
}
|
||||
|
||||
@objc private func editRemarkTapped() { onEditRemark?() }
|
||||
@objc private func editCommissionTapped() { onEditCommission?() }
|
||||
@objc private func callTapped() { onCall?() }
|
||||
}
|
||||
|
||||
/// 标签-值行。
|
||||
final class CooperationInfoRowView: UIView {
|
||||
private let titleLabel = UILabel()
|
||||
private let valueLabel = UILabel()
|
||||
|
||||
init(label: String) {
|
||||
super.init(frame: .zero)
|
||||
titleLabel.text = label
|
||||
titleLabel.font = .systemFont(ofSize: 13)
|
||||
titleLabel.textColor = AppColor.textTertiary
|
||||
valueLabel.font = .systemFont(ofSize: 13)
|
||||
valueLabel.textColor = AppColor.textSecondary
|
||||
valueLabel.numberOfLines = 0
|
||||
|
||||
addSubview(titleLabel)
|
||||
addSubview(valueLabel)
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.top.bottom.equalToSuperview()
|
||||
make.width.equalTo(72)
|
||||
}
|
||||
valueLabel.snp.makeConstraints { make in
|
||||
make.leading.equalTo(titleLabel.snp.trailing).offset(12)
|
||||
make.trailing.top.bottom.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setValue(_ value: String) {
|
||||
valueLabel.text = value
|
||||
}
|
||||
}
|
||||
|
||||
enum CooperationOrderPhoneMask {
|
||||
static func mask(_ phone: String) -> String {
|
||||
let digits = phone.filter(\.isNumber)
|
||||
guard digits.count >= 7 else { return phone }
|
||||
return "\(digits.prefix(3))****\(digits.suffix(4))"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user