Add OrderTokens and shared order components, restore filter bar pills, list loading states, and role-specific card layouts with deposit badge mapping tests. Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
4.2 KiB
Swift
124 lines
4.2 KiB
Swift
//
|
||
// OrderListView.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 订单列表容器,含下拉刷新、空态与分页,对齐 Android LazyColumn。
|
||
final class OrderListView: UIView {
|
||
|
||
var onRefresh: (() -> Void)?
|
||
var onLoadMore: ((Int) -> Void)?
|
||
|
||
let tableView = UITableView(frame: .zero, style: .plain)
|
||
private let refreshControl = UIRefreshControl()
|
||
private let loadingIndicator = UIActivityIndicatorView(style: .medium)
|
||
private let emptyLabel = UILabel()
|
||
private let footerLabel = UILabel()
|
||
private let footerSpinner = UIActivityIndicatorView(style: .medium)
|
||
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
backgroundColor = AppColor.pageBackground
|
||
tableView.backgroundColor = .clear
|
||
tableView.separatorStyle = .none
|
||
tableView.keyboardDismissMode = .onDrag
|
||
tableView.contentInset = UIEdgeInsets(top: OrderTokens.cardGap, left: 0, bottom: OrderTokens.cardGap, right: 0)
|
||
tableView.refreshControl = refreshControl
|
||
refreshControl.addTarget(self, action: #selector(refreshTriggered), for: .valueChanged)
|
||
|
||
loadingIndicator.hidesWhenStopped = true
|
||
loadingIndicator.color = AppColor.primary
|
||
|
||
emptyLabel.font = .app(.body)
|
||
emptyLabel.textColor = OrderTokens.label563
|
||
emptyLabel.text = "暂无订单"
|
||
emptyLabel.textAlignment = .center
|
||
|
||
footerLabel.font = .app(.body)
|
||
footerLabel.textColor = OrderTokens.label563
|
||
footerLabel.textAlignment = .center
|
||
footerLabel.text = "没有更多"
|
||
|
||
footerSpinner.hidesWhenStopped = true
|
||
footerSpinner.color = AppColor.primary
|
||
|
||
addSubview(tableView)
|
||
addSubview(loadingIndicator)
|
||
tableView.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
loadingIndicator.snp.makeConstraints { make in
|
||
make.center.equalToSuperview()
|
||
}
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
func apply(
|
||
isRefreshing: Bool,
|
||
canLoadMore: Bool,
|
||
isEmpty: Bool,
|
||
isLoading: Bool = false,
|
||
isLoadingMore: Bool = false
|
||
) {
|
||
if isRefreshing {
|
||
if !refreshControl.isRefreshing { refreshControl.beginRefreshing() }
|
||
} else {
|
||
refreshControl.endRefreshing()
|
||
}
|
||
|
||
if isLoading, isEmpty {
|
||
loadingIndicator.startAnimating()
|
||
tableView.backgroundView = nil
|
||
tableView.tableFooterView = nil
|
||
} else {
|
||
loadingIndicator.stopAnimating()
|
||
if isEmpty {
|
||
tableView.backgroundView = emptyBackgroundView()
|
||
} else {
|
||
tableView.backgroundView = nil
|
||
}
|
||
tableView.tableFooterView = footerView(canLoadMore: canLoadMore, isLoadingMore: isLoadingMore, isEmpty: isEmpty)
|
||
}
|
||
}
|
||
|
||
private func emptyBackgroundView() -> UIView {
|
||
let container = UIView(frame: tableView.bounds)
|
||
emptyLabel.frame = container.bounds
|
||
emptyLabel.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||
container.addSubview(emptyLabel)
|
||
return container
|
||
}
|
||
|
||
private func footerView(canLoadMore: Bool, isLoadingMore: Bool, isEmpty: Bool) -> UIView? {
|
||
guard !isEmpty else { return nil }
|
||
if isLoadingMore {
|
||
let container = UIView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: 44))
|
||
footerSpinner.center = CGPoint(x: container.bounds.midX, y: container.bounds.midY)
|
||
footerSpinner.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
|
||
container.addSubview(footerSpinner)
|
||
footerSpinner.startAnimating()
|
||
return container
|
||
}
|
||
footerSpinner.stopAnimating()
|
||
guard !canLoadMore else { return nil }
|
||
footerLabel.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 44)
|
||
return footerLabel
|
||
}
|
||
|
||
@objc private func refreshTriggered() {
|
||
onRefresh?()
|
||
}
|
||
|
||
func notifyLoadMoreIfNeeded(at index: Int, shouldLoad: Bool) {
|
||
guard shouldLoad else { return }
|
||
onLoadMore?(index)
|
||
}
|
||
}
|