Files
suixinkan_uikit/suixinkan/UI/Task/TaskOrderSelectViewController.swift
lujiuyinandCursor 005349f8e6 模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 11:01:08 +08:00

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
}
}