将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
254 lines
9.2 KiB
Swift
254 lines
9.2 KiB
Swift
//
|
|
// TaskOrderSelectViewController.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import SnapKit
|
|
import UIKit
|
|
|
|
/// 任务关联订单选择页,对齐 Android `OrderSelectScreen`。
|
|
final class TaskOrderSelectViewController: BaseViewController {
|
|
|
|
var onConfirmed: ((String?) -> Void)?
|
|
|
|
private let viewModel: TaskOrderSelectViewModel
|
|
private let taskAPI = NetworkServices.shared.taskAPI
|
|
|
|
private let tableView = UITableView(frame: .zero, style: .plain)
|
|
private let confirmButton = AppButton(title: "确定")
|
|
private let emptyLabel = UILabel()
|
|
private let bottomBar = UIView()
|
|
|
|
init(initialOrderNumber: String? = nil) {
|
|
viewModel = TaskOrderSelectViewModel(initialOrderNumber: initialOrderNumber)
|
|
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 viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
ProfileNavigationStyle.apply(to: navigationController, showsDivider: false)
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
ProfileNavigationStyle.restore(navigationController)
|
|
}
|
|
|
|
override func setupUI() {
|
|
tableView.backgroundColor = AppColor.pageBackground
|
|
tableView.separatorStyle = .none
|
|
tableView.register(TaskOrderCell.self, forCellReuseIdentifier: TaskOrderCell.reuseIdentifier)
|
|
tableView.dataSource = self
|
|
tableView.delegate = self
|
|
tableView.contentInset = UIEdgeInsets(top: 12, left: 0, bottom: 0, right: 0)
|
|
|
|
emptyLabel.text = "无有效订单"
|
|
emptyLabel.font = .app(.body)
|
|
emptyLabel.textColor = AppColor.textPrimary
|
|
emptyLabel.textAlignment = .center
|
|
emptyLabel.isHidden = true
|
|
|
|
bottomBar.backgroundColor = .white
|
|
view.addSubview(tableView)
|
|
view.addSubview(emptyLabel)
|
|
view.addSubview(bottomBar)
|
|
bottomBar.addSubview(confirmButton)
|
|
confirmButton.snp.updateConstraints { make in make.height.equalTo(48) }
|
|
}
|
|
|
|
override func setupConstraints() {
|
|
bottomBar.snp.makeConstraints { make in
|
|
make.leading.trailing.bottom.equalToSuperview()
|
|
}
|
|
confirmButton.snp.makeConstraints { make in
|
|
make.top.equalToSuperview().offset(16)
|
|
make.leading.trailing.equalToSuperview().inset(16)
|
|
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
|
|
}
|
|
tableView.snp.makeConstraints { make in
|
|
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
|
|
make.bottom.equalTo(bottomBar.snp.top)
|
|
}
|
|
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) }
|
|
}
|
|
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
Task { await viewModel.loadOrders(api: taskAPI) }
|
|
}
|
|
|
|
@MainActor
|
|
private func applyViewModel() {
|
|
emptyLabel.isHidden = viewModel.isLoading || !viewModel.orders.isEmpty
|
|
tableView.reloadData()
|
|
if viewModel.isLoading {
|
|
showLoading()
|
|
} else {
|
|
hideLoading()
|
|
}
|
|
}
|
|
|
|
@objc private func confirmTapped() {
|
|
onConfirmed?(viewModel.confirmedOrderNumber())
|
|
navigationController?.popViewController(animated: true)
|
|
}
|
|
}
|
|
|
|
extension TaskOrderSelectViewController: UITableViewDataSource, UITableViewDelegate {
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
viewModel.orders.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cell = tableView.dequeueReusableCell(
|
|
withIdentifier: TaskOrderCell.reuseIdentifier,
|
|
for: indexPath
|
|
) as! TaskOrderCell
|
|
let order = viewModel.orders[indexPath.row]
|
|
cell.apply(
|
|
order: order,
|
|
isSelected: viewModel.selectedOrderNumber == order.orderNumber
|
|
)
|
|
return cell
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
viewModel.toggleSelection(orderNumber: viewModel.orders[indexPath.row].orderNumber)
|
|
}
|
|
}
|
|
|
|
/// 订单选择卡片 cell。
|
|
private final class TaskOrderCell: UITableViewCell {
|
|
|
|
static let reuseIdentifier = "TaskOrderCell"
|
|
|
|
private let cardView = UIView()
|
|
private let projectRow = TaskInfoRowView(label: "项目名称:")
|
|
private let statusRow = TaskInfoRowView(label: "订单状态:")
|
|
private let numberRow = TaskInfoRowView(label: "订单编号:")
|
|
private let payTimeRow = TaskInfoRowView(label: "付款时间:")
|
|
private let userIDRow = TaskInfoRowView(label: "用户 UID:")
|
|
private let phoneRow = TaskInfoRowView(label: "用户手机号:")
|
|
|
|
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.sm
|
|
cardView.layer.borderWidth = 1
|
|
cardView.layer.borderColor = AppColor.border.cgColor
|
|
|
|
contentView.addSubview(cardView)
|
|
cardView.addSubview(projectRow)
|
|
cardView.addSubview(statusRow)
|
|
cardView.addSubview(numberRow)
|
|
cardView.addSubview(payTimeRow)
|
|
cardView.addSubview(userIDRow)
|
|
cardView.addSubview(phoneRow)
|
|
|
|
cardView.snp.makeConstraints { make in
|
|
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md))
|
|
}
|
|
projectRow.snp.makeConstraints { make in
|
|
make.top.leading.trailing.equalToSuperview().inset(AppSpacing.md)
|
|
}
|
|
statusRow.snp.makeConstraints { make in
|
|
make.top.equalTo(projectRow.snp.bottom).offset(AppSpacing.xs)
|
|
make.leading.trailing.equalTo(projectRow)
|
|
}
|
|
numberRow.snp.makeConstraints { make in
|
|
make.top.equalTo(statusRow.snp.bottom).offset(AppSpacing.xs)
|
|
make.leading.trailing.equalTo(projectRow)
|
|
}
|
|
payTimeRow.snp.makeConstraints { make in
|
|
make.top.equalTo(numberRow.snp.bottom).offset(AppSpacing.xs)
|
|
make.leading.trailing.equalTo(projectRow)
|
|
}
|
|
userIDRow.snp.makeConstraints { make in
|
|
make.top.equalTo(payTimeRow.snp.bottom).offset(AppSpacing.xs)
|
|
make.leading.trailing.equalTo(projectRow)
|
|
}
|
|
phoneRow.snp.makeConstraints { make in
|
|
make.top.equalTo(userIDRow.snp.bottom).offset(AppSpacing.xs)
|
|
make.leading.trailing.bottom.equalToSuperview().inset(AppSpacing.md)
|
|
}
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
func apply(order: AvailableOrder, isSelected: Bool) {
|
|
projectRow.apply(value: order.projectName.isEmpty ? "--" : order.projectName)
|
|
statusRow.apply(value: order.orderStatusLabel.isEmpty ? "--" : order.orderStatusLabel)
|
|
numberRow.apply(value: order.orderNumber)
|
|
payTimeRow.apply(value: order.payTime.isEmpty ? "--" : order.payTime)
|
|
userIDRow.apply(value: "U\(order.userId)")
|
|
phoneRow.apply(value: order.userPhone)
|
|
cardView.layer.borderColor = isSelected ? UIColor(hex: 0xFF0000).cgColor : AppColor.border.cgColor
|
|
cardView.layer.borderWidth = isSelected ? 2 : 1
|
|
accessibilityLabel = "订单\(order.orderNumber)"
|
|
accessibilityTraits = isSelected ? [.button, .selected] : .button
|
|
}
|
|
}
|
|
|
|
/// 订单信息行。
|
|
private final class TaskInfoRowView: UIView {
|
|
private let labelView = UILabel()
|
|
private let valueView = UILabel()
|
|
|
|
init(label: String) {
|
|
super.init(frame: .zero)
|
|
labelView.text = label
|
|
labelView.font = .app(.body)
|
|
labelView.textColor = AppColor.textPrimary
|
|
valueView.font = .app(.body)
|
|
valueView.textColor = AppColor.textPrimary
|
|
valueView.numberOfLines = 0
|
|
|
|
addSubview(labelView)
|
|
addSubview(valueView)
|
|
labelView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
labelView.snp.makeConstraints { make in make.leading.top.bottom.equalToSuperview() }
|
|
valueView.snp.makeConstraints { make in
|
|
make.leading.equalTo(labelView.snp.trailing).offset(AppSpacing.xs)
|
|
make.trailing.top.bottom.equalToSuperview()
|
|
}
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
func apply(value: String) {
|
|
valueView.text = value
|
|
}
|
|
}
|